unittest的四大特点
-
TestCase:测试用例。所有的用例都是直接继承与UnitTest.TestCase类。
-
TestFixture:测试固件。setUp和tearDown分别作为前置条件和后置条件。主要用于初始化测试用例和管理用例执行结束后的资源释放。
-
TestSuite和TestRunner:测试套件和测试运行器。
-
断言:在unittest中封装好了成熟的断言,可以直接调用。
unittest基本用法
-
语法规则
-
unittest中,测试用例的名称,必须以“test_”开头。否则即便定义了测试用例也不会被执行。
-
用例的执行顺序与定义的先后顺序无关,而是根据用例名称来排序执行的。如test_1会比test_2先执行。
-
-
所有测试用例的运行
-
在main代码块内调用unittest.main()
-
1 import unittest 2 3 4 class TestDemo(unittest.TestCase): 5 6 def setUp(self) -> None: 7 print('执行每个测试用例前的准备工作') 8 9 def tearDown(self) -> None: 10 print('执行完每个测试用例后的收尾工作') 11 12 def test_1(self): 13 print('测试用例1') 14 15 def test_2(self): 16 print('测试用例2') 17 18 def a(self): 19 print('不按命名规则的测试用例') 20 21 22 if __name__ == '__main__': 23 unittest.main() 24 25 26 27 执行结果: 28 29 执行每个测试用例前的准备工作 30 测试用例1 31 执行完每个测试用例后的收尾工作 32 执行每个测试用例前的准备工作 33 测试用例2 34 执行完每个测试用例后的收尾工作 35 .. 36 ---------------------------------------------------------------------- 37 Ran 2 tests in 0.000s 38 39 OK
数据驱动测试
-
ddt模块的使用
-
ddt模块的data装饰器可以给测试用例传入数据
-
需要使用ddt模块给测试用例传入数据,需要先使用ddt装饰器给对应的测试用例的类进行装饰
-
1 import unittest 2 from ddt import ddt, data 3 4 5 @ddt 6 class TestDemo(unittest.TestCase): 7 8 def setUp(self) -> None: 9 print('执行每个测试用例前的准备工作') 10 11 def tearDown(self) -> None: 12 print('执行完每个测试用例后的收尾工作') 13 14 @data('我是传入的数据') 15 def test_1(self, data): 16 print(data) 17 18 19 if __name__ == '__main__': 20 unittest.main()
-
注意点
-
如果data装饰器传入多条数据,表示该用例会被执行多少次,每次使用不同的数据来测试
1 import unittest 2 from ddt import ddt, data 3 4 5 @ddt 6 class TestDemo(unittest.TestCase): 7 8 def setUp(self) -> None: 9 print('执行每个测试用例前的准备工作') 10 11 def tearDown(self) -> None: 12 print('执行完每个测试用例后的收尾工作') 13 14 @data('我是传入的数据1', '我是传入的数据2') 15 def test_1(self, data1): 16 print(data1) 17 18 19 if __name__ == '__main__': 20 unittest.main() 21 22 执行结果: 23 执行每个测试用例前的准备工作 24 我是传入的数据1 25 执行完每个测试用例后的收尾工作 26 执行每个测试用例前的准备工作 27 我是传入的数据2 28 执行完每个测试用例后的收尾工作 29 .. 30 ---------------------------------------------------------------------- 31 Ran 2 tests in 0.000s 32 33 OK
-
如果测试用例的函数需要接收多个参数,需要将每一组数据放在列表中传入
-
需要特别注意的是,即便使用列表将多个参数对应的数据传入测试用例。整个列表还是会被当做是一个整体默认传给第一个参数,此时使用*[]语法是错误的,这样列表中的元素会被拆分成一个个独立的元素,作为每一次测试使用的测试数据。
-
此时需要使用ddt模块中的例外一个装饰器—unpack。它会负责将列表内的元素对应的传给参数。
-
1 import unittest 2 from ddt import ddt, data, unpack 3 4 5 @ddt 6 class TestDemo(unittest.TestCase): 7 8 def setUp(self) -> None: 9 print('执行每个测试用例前的准备工作') 10 11 def tearDown(self) -> None: 12 print('执行完每个测试用例后的收尾工作') 13 14 @data(['我是传入的数据1', '我是传入的数据2'], ['第二组用例的数据1', '第二组用例的数据2']) 15 @unpack 16 def test_1(self, data1, data2): 17 print(data1) 18 print(data2) 19 20 21 if __name__ == '__main__': 22 unittest.main() 23 24 25 执行结果: 26 执行每个测试用例前的准备工作 27 我是传入的数据1 28 我是传入的数据2 29 执行完每个测试用例后的收尾工作 30 执行每个测试用例前的准备工作 31 第二组用例的数据1 32 第二组用例的数据2 33 执行完每个测试用例后的收尾工作 34 .. 35 ---------------------------------------------------------------------- 36 Ran 2 tests in 0.000s 37 38 OK
-
断言
-
断言就是一个测试用例预期结果与实际结果的对比(实际结果是否与预期符合)
-
unittest的常用的一些断言:
# 判断 a == b,msg表示错误提示信息 self.assertEqual(a, b, msg='a不等于b') # 判断 a != b self.assertNotEqual(a, b) # 判断 bool(x) is True self.assertTrue(x) # 判断 bool(x) is False self.assertFalse(x) # 判断 a is b self.assertIs(a, b) # 判断 a is not b self.assertIsNot(a, b) # 判断 x is None self.assertIsNone(x) # 判断 x is not None self.assertIsNotNone(x) # 判断 a in b self.assertIn(a, b) # 判断 a not in b self.assertNotIn(a, b) # 判断 isinstance(a, b) self.assertIsInstance(a, b) # 判断 not isinstance(a, b) self.assertNotIsInstance(a, b)
Skip操作
-
将某些暂时不需要执行的测试用例跳过
1 @unittest.skip('无条件跳过') 2 def test_1(self, data1, data2): 3 print(data1) 4 print(data2) 5 self.assertEqual(1, 2, msg='1不等于2')
-
给指定测试用例加上skip装饰器,即可无条件跳过该测试用例
-
有条件限制的跳过测试用例:
-
skipUnless装饰器
-
当条件为False时会跳过当前测试用例
1 @unittest.skipUnless(1 > 2, '条件为假跳过') 2 def test_2(self): 3 print(111)
-
-
skipIf装饰器
-
当条件为True时会跳过当前测试用例
1 @unittest.skipIf(2 > 1, '条件为真时跳过') 2 def test_3(self): 3 print(222)
-
-
-
测试用例断言为假时(与预期结果不符)标记该测试用例
-
expectedFailure装饰器
1 @unittest.expectedFailure 2 def test_4(self): 3 self.assertEqual(1, 2, '1不等于2') 4 5 该测试用例被标记为: 6 expected failures=1 7 注意: 8 和skip不同,skip是根据条件或者无条件跳过不执行测试用例 9 expectedFailure是执行完测试用例后,将与预期不符的测试用例标记出来
使用了该装饰器的测试用例,断言结果必须是错误的。不允许成功
否则会出现以下错误:
Test should not succeed since it's marked with @unittest.expectedFailure
-
测试套件和测试运行器
-
测试套件
-
将想要执行的测试用例加入到一个测试套件内,然后通过运行器执行测试套件内的测试用例,即可实现执行部分测试用例。
-
测试套件不能与测试用例在一个文件内,否则即使使用测试运行器运行测试套件,还是会执行所有用例。
1 import unittest 2 from test import TestDemo 3 4 # 实例化测试套件 5 suite = unittest.TestSuite() 6 # 添加测试用例 7 suite.addTest(TestDemo('test_1')) 8 suite.addTest(TestDemo('test_3')) 9 10 # 实例化测试运行器 11 runner = unittest.TextTestRunner() 12 runner.run(suite)
-
-
往测试套件内添加测试用例的多种方式:
-
上述例子中,直接通过指定测试用例类中的测试用例方法名,将测试用例添加至套件内。
-
上述例子添加用例的方式可优化:
1 cases = [TestDemo('test_1'), TestDemo('test_3')] 2 suite.addTests(cases)
-
-
通过正则匹配查询,将指定路径下所有匹配成功的测试用例全都加入测试套件内
1 discover = unittest.defaultTestLoader.discover(start_dir='./', pattern='test*.py') 2 3 # 实例化测试运行器 4 runner = unittest.TextTestRunner() 5 runner.run(discover) 6 7 # 此时运行器执行的是从‘./’路径下,也就是当前路径下所有与‘test*.py’相匹配的测试用例文件内的测试用例的集合。
-
直接将指定测试用例类对象中的所有测试用例添加至套件内
1 suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestDemo))
-
将上述方式改为通过类名称方式将所有用例添加至套件内
1 suite.addTests(unittest.TestLoader().loadTestsFromName('test.TestDemo')) 2 3 # 注意类名称必须通过指定包/模块加‘.’的形式
-
-
测试运行器
-
主要用于执行测试套件
-
HTMLTestRunner
-
用于生成测试报告
-
报告内只记录通过的用例和失败的用例,没有跳过的用例。
-
-
HTMLTestRunner配置
-
由于下载的HTMLTestRunner.py文件是基于Python2的,若适用于Python3则进行如下修改:
-
94行, import StringIO
-
539行,self.outputBuffer = StringIO.StringIO()
-
631行,print >>sys.stderr, ' Time Elapsed: %s' % (self.stopTime-self.startTime)
-
642行,if not rmap.has_key(cls):
-
766行,uo = o.decode('latin-1')
-
772行,ue = e.decode('latin-1')
-
-
将下载的文件保存到:PythonXXLib目录下。
-
基本用法
1 import unittest 2 from HTMLTestRunner import HTMLTestRunner 3 from test import TestDemo 4 5 # 实例化测试套件 6 suite = unittest.TestSuite() 7 8 # 生成测试报告 9 report_name = '测试报告名称.html' 10 report_title = '测试报告标题' 11 report_desc = '测试报告描述' 12 report_path = './' 13 report_file = report_path + report_name 14 15 with open(report_file, 'wb') as report: 16 suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestDemo)) 17 runner = HTMLTestRunner(stream=report, title=report_title, description=report_desc) 18 runner.run(suite)