全面介绍python中很常用的单元测试框架unitest


Posted in Python onDecember 14, 2020

1、unitest主要功能模块介绍

unitest主要包含TestCase、TestSuite、TestLoader、TextTestRunner、TextTestResult这几个功能模块。

  • TestCase:一个TestCase实例就是一个测试用例,一个测试用例就是一个完整的测试流程,包括测试前环境的搭建,测试代码的执行,以及测试后环境的还原或者销毁。元测试的本质也就在这里,一个测试用例是一个完整的测试单元,可以对某一具体问题进行检查验证。
  • TestSuite:多个测试用例集合在一起就是TestSuite,TestSuite也可以嵌套TestSuite。
  • TestLoader:TestLoader的作用是将Testcase加载到TestSuite中。
  • TextTestRunner:TextTestRunner是用来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。
  • TextTestResult:TextTestResult用来保存测试结果,其中包括运行了多少测试用例,成功了多少,失败了多少等信息。

整个流程为:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中。

2、实例介绍

首先准备几个待测的方法,写在test_func.py中。

def add(a, b):
  return a + b


def multi(a, b):
  return a * b


def lower_str(string):
  return string.lower()


def square(x):
  return x ** 2

准备好几个待测的方法之后,为这些方法写一个测试用例,写入our_testcase.py中。

import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) # 这里故意设计一个会出错的用例,测试4的平方等于17,实际上并不等于。
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main()

这里写好之后,进入命令行终端,执行python our_testcase.py,执行结果如下。

...F
======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)

这里分析一下这个执行结果。首先能够看到一共运行了4个测试用例,失败了1个,并且给出了失败原因,AssertionError: 17 != 16,这是我们故意留下的错误漏洞,被测试用例测试出来了。

第一行...F中,一个点.代表测试成功,F代表失败,我们的测试结果中,前三个成功了,第四个失败了,总共是四个测试,其余的符号中E代表出错,S代表跳过。

特别说明的一点是,测试的执行顺序跟方法的顺序没有关系,四个测试是随机先后执行的。

每个测试方法编写的时候,都要以test开头,比如test_square,否则是不被unitest识别的。

在unitest.main()中加上verbosity参数可以控制输出的错误报告的详细程序,默认是1,如果设为0,则不输出每一用例的执行结果,即上面的第一行的执行结果内容。如果设为2,则输出详细的执行结果。

修改our_testcase.py中主函数。

if __name__ == '__main__':
  unittest.main(verbosity=2)

执行结果如下。

test_add (__main__.TestFunc)
Test func add ... ok
test_lower_str (__main__.TestFunc)
Test func lower_str ... ok
test_multi (__main__.TestFunc)
Test func multi ... ok
test_square (__main__.TestFunc)
Test func square ... FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)

可以看到,每一个用例的详细执行情况以及用例名,用例描述均被输出了出来,在测试方法下加代码示例中的"""Doc String""",在用例执行时,会将该字符串作为此用例的描述,加合适的注释能够使输出的测试报告更加便于阅读。

3、组织TestSuite

按照上面的测试方法,我们无法控制用例执行的顺序,这样显然是不合理的,因为在一些测试过程中,我们肯定需要控制先测试某些用例,再测试某些用例,这些用例有先后的因果关系。在这里,我们就需要用到TestSuite。我们添加到TestSuite中的case是会按照添加的顺序执行的。

还有一个问题是,我们现在只有一个测试文件,我们直接执行该文件即可,但如果有多个测试文件,怎么进行组织,总不能一个个文件执行吧,答案也在TestSuite中。

新建一个文件,test_suite.py。

import unittest
from our_testcase import TestFunc

if __name__ == '__main__':
  suite = unittest.TestSuite()
  tests = [TestFunc("test_square"), TestFunc("test_lower_str"), TestFunc("test_multi")]
  suite.addTests(tests)
  runner = unittest.TextTestRunner(verbosity=2)
  runner.run(suite)

执行结果如下。

test_square (our_testcase.TestFunc)
Test func square ... FAIL
test_lower_str (our_testcase.TestFunc)
Test func lower_str ... ok
test_multi (our_testcase.TestFunc)
Test func multi ... ok

======================================================================
FAIL: test_square (our_testcase.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/Users/luyuze/projects/test/our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1)

这样,用例执行的顺序就是按照我们添加进去的顺序来执行的了。

上面使用的是TestSuite的addTests()方法,并直接传入TestCase列表,也有一些其他的方法可以向TestSuite中添加用例。

# 直接用addTest方法添加单个TestCase
suite.addTest(TestMathFunc("test_multi"))


# 使用loadTestFromName,传入模块名.TestCase名,下面俩方法效果相同
suite.addTests(unittest.TestLoader().loadTestsFromName('our_testcase.TestFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['our_testcase.TestFunc']))


# loadTestsFromTestCase(),传入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunc))

用TestLoader的方法是无法对case进行排序的,同时,suite中也可以套suite。

4、输出文件

用例组织好了,但是结果只能输出到控制台,这样没办法查看之前的执行记录,我们想将结果输出到文件。

修改test_suite.py。

import unittest
from our_testcase import TestFunc

if __name__ == '__main__':
  suite = unittest.TestSuite()
  tests = [TestFunc("test_square"), TestFunc("test_lower_str"), TestFunc("test_multi")]
  suite.addTests(tests)

  with open('UnitestTextReport.txt', 'a') as f:
    runner = unittest.TextTestRunner(stream=f, verbosity=2)
    runner.run(suite)

5、测试前后的处理

在之前的测试中,可能会存在这样的问题:如果要在测试之前准备环境,测试完成之后做一些清理怎么办?这里需要用到的是setUp()和tearDown()。

修改our_testcase.py。

import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""
  
  def setUp(self):
    print("do something before testcase")

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4))
    self.assertNotEqual(35, square(6))
    
  def tearDownClass(self):
    print("do something after testcase")


if __name__ == '__main__':
  unittest.main(verbosity=2)

执行结果:

test_add (__main__.TestFunc)
Test func add ... do something before testcase
do something after testcase
ok
test_lower_str (__main__.TestFunc)
Test func lower_str ... do something before testcase
do something after testcase
ok
test_multi (__main__.TestFunc)
Test func multi ... do something before testcase
do something after testcase
ok
test_square (__main__.TestFunc)
Test func square ... do something before testcase
do something after testcase
FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 30, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

可以发现setUp()和tearDown()在每个case前后都执行了一次。如果要在所有case执行之前和所有case执行之后准备和清理环境,我们可以使用setUpClass() 与 tearDownClass()。

class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  @classmethod
  def setUpClass(cls):
    print "This setUpClass() method only called once."

  @classmethod
  def tearDownClass(cls):
    print "This tearDownClass() method only called once too."

6、跳过case

如果我们临时想要跳过某个case不执行,unitest也有相应的方法。

1、skip装饰器

# -*- coding: utf-8 -*-

import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  @unittest.skip('do not run this case')
  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4))
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main(verbosity=2)

执行结果:

test_add (__main__.TestFunc)
Test func add ... skipped 'do not run this case'
test_lower_str (__main__.TestFunc)
Test func lower_str ... ok
test_multi (__main__.TestFunc)
Test func multi ... ok
test_square (__main__.TestFunc)
Test func square ... FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 28, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1, skipped=1)

结果显示为,总共执行4个测试,1个失败,1个被跳过。

skip装饰器一共有三个 unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过。

2、TestCase.skipTest()方法

class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.skipTest("do not run this case")
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

效果与第一种是一样的。

以上就是全面介绍python中很常用的单元测试框架unitest的详细内容,更多关于python 单元测试框架unitest的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
进一步理解Python中的函数编程
Apr 13 Python
浅谈Python 集合(set)类型的操作——并交差
Jun 30 Python
python 计算文件的md5值实例
Jan 13 Python
使用Python对Access读写操作
Mar 30 Python
Python实现删除文件中含“指定内容”的行示例
Jun 09 Python
python模块之sys模块和序列化模块(实例讲解)
Sep 13 Python
python实现图书借阅系统
Feb 20 Python
利用PyCharm Profile分析异步爬虫效率详解
May 08 Python
Python操作Sqlite正确实现方法解析
Feb 05 Python
Python Django中间件使用原理及流程分析
Jun 13 Python
PyCharm2020.1.1与Python3.7.7的安装教程图文详解
Aug 07 Python
python 实现Requests发送带cookies的请求
Feb 08 Python
python读写数据读写csv文件(pandas用法)
Dec 14 #Python
详解Python中@staticmethod和@classmethod区别及使用示例代码
Dec 14 #Python
Python 找出英文单词列表(list)中最长单词链
Dec 14 #Python
Python 排序最长英文单词链(列表中前一个单词末字母是下一个单词的首字母)
Dec 14 #Python
Python实现Kerberos用户的增删改查操作
Dec 14 #Python
python-地图可视化组件folium的操作
Dec 14 #Python
python多线程和多进程关系详解
Dec 14 #Python
You might like
PHP封装的PDO数据库操作类实例
2017/06/21 PHP
Js之软键盘实现(js源码)
2007/01/30 Javascript
jsp+javascript打造级连菜单的实例代码
2013/06/14 Javascript
js计算德州扑克牌面值的方法
2015/03/04 Javascript
JQuery复制DOM节点的方法
2015/06/11 Javascript
JavaScript实现对下拉列表值进行排序的方法
2015/07/15 Javascript
jQuery跨域问题解决方案
2015/08/03 Javascript
JS打字效果的动态菜单代码分享
2015/08/21 Javascript
Javascript的表单验证-提交表单
2016/03/18 Javascript
js实现用户输入的小写字母自动转大写字母的方法
2017/01/21 Javascript
深入学习nodejs中的async模块的使用方法
2017/07/12 NodeJs
vue-router路由懒加载和权限控制详解
2017/12/13 Javascript
webpack-dev-server远程访问配置方法
2018/02/22 Javascript
vue中进行微博分享的实例讲解
2019/10/14 Javascript
微信小程序之滑动页面隐藏和显示组件功能的实现代码
2020/06/19 Javascript
Element Popover 弹出框的使用示例
2020/07/26 Javascript
[03:36]2014DOTA2 TI小组赛综述 八强诞生进军钥匙球馆
2014/07/15 DOTA
进一步了解Python中的XML 工具
2015/04/13 Python
python虚拟环境virtualenv的使用教程
2017/10/20 Python
Python测试人员需要掌握的知识
2018/02/08 Python
Python基于FTP模块实现ftp文件上传操作示例
2018/04/23 Python
78行Python代码实现现微信撤回消息功能
2018/07/26 Python
Pytorch 多块GPU的使用详解
2019/12/31 Python
pytorch:model.train和model.eval用法及区别详解
2020/02/20 Python
Django添加bootstrap框架时无法加载静态文件的解决方式
2020/03/27 Python
Pyspark获取并处理RDD数据代码实例
2020/03/27 Python
pip安装提示Twisted错误问题(Python3.6.4安装Twisted错误)
2020/05/09 Python
Python3爬虫中识别图形验证码的实例讲解
2020/07/30 Python
Sam’s Club山姆会员商店:沃尔玛旗下高端会员制商店
2017/01/16 全球购物
万宝龙英国官网:Montblanc手表、书写工具、皮革和珠宝
2018/10/16 全球购物
Shell编程面试题
2016/05/29 面试题
应届毕业生如何写求职信
2014/02/16 职场文书
2014年两会学习心得范例
2014/03/17 职场文书
《认识年月日》教学反思
2016/02/19 职场文书
php引用传递
2021/04/01 PHP
Opencv中cv2.floodFill算法的使用
2021/06/18 Python