文章目录
- 概述
- 测试用例
- 测试夹具
- 断言
- 测试套件
- 添加测试用例
- 测试用例加载器
- 测试执行者
- 用例执行结果
- 其它主题
- 总结
这篇笔记记录了Python内建模块unittest的知识点,所记录的内容均来自unittest的官方文档。
概述
unittest是Python标准库中的一个模块,使用它可以对Python代码进行单元测试,它支持如下特性:
- 测试自动化;
- 共享测试夹具(即测试用例的setUp()和tearDown()代码);
- 将测试用例组织成集合;
- 测试用例和测试报告框架之间相独立;
为了支持上述特性,unittest实现了如下几个重要的概念:
- 测试夹具
就是大家熟悉的setUp()和tearDown()机制。可以在Seup()中执行一些用例执行的准备工作,对应的在tearDown()中执行一些清理工作。 - 测试用例
这是单元测试最基本的内容,测试用例中构造输入参数,执行操作,然后检查结果是否符合预期。unittest中,每个测试用例都是一个TestCase对象。 - 测试套件
测试套件就是测试用例的集合,使用测试套件,可以自由的组合测试用例,以及控制它们的执行顺序。 - 测试执行者
负责执行测试用例,并且展示测试结果的组件。
测试用例
首先,一个个的测试用例函数要组织成类成员函数,这个类要继承自unittest.TestCase,举例如下:
import unittest
# 包含测试用例的类要继承自unittest.TestCase
class TestStringMethods(unittest.TestCase):
# 1)测试用例函数必须要以test_开头,这样才会被框架认为是一条测试用例;
# 2)测试用例函数只能含有一个self参数
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
if __name__ == '__main__':
# unittest.main()会自动识别本模块中的测试用例并执行
unittest.main()
测试夹具
有时候,我们需要在测试用例执行之前,执行一些准备工作,比如打开文件;在测试用例执行完毕后,执行一些清理工作,比如关闭文件,这时候测试夹具就派上了用场。
import unittest
class WidgetTestCase(unittest.TestCase):
# 该函数在每个测试用例执行之前都会被执行
def setUp(self):
self.widget = Widget('The widget')
def test_default_widget_size(self):
self.assertEqual(self.widget.size(), (50,50),
'incorrect default size')
def test_widget_resize(self):
self.widget.resize(100,150)
self.assertEqual(self.widget.size(), (100,150),
'wrong size after resize')
# 只要setUp()执行成功,那么每条用例执行完毕后tearDown()都会被执行,不管用例是否执行成功
def tearDown(self):
self.widget.dispose()
如上,setUp()和tearDown()实际上都是父类unittest.TestCase的方法,子类属于重写,每个类只有一个该方法,所以如果一个类中定义了多条测试用例,那么这些测试用例是共享setUp()和tearDown()的,这就是概述中所说的unittest是共享测试夹具的。
unittest还针对类和模块提供了夹具,即setUpClass()、tearDownClass()和setUpModule()、tearDownModule(),具体见Class and Module Fixtures。
断言
测试用例编写过程中,其中最最关键的就是比较目标对象的运行结果和预期值,如果二者不一致,那么就是测试失败,在单元测试中,这种比较操作都是使用断言。unittest提供了丰富的断言,比如最基础的如下表所示,更具体的见手册。
Method | Checks that | New in |
assertEqual(a, b) | a == b | |
assertNotEqual(a, b) | a != b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a, b) | a is b | 3.1 |
assertIsNot(a, b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a, b) | a in b | 3.1 |
assertNotIn(a, b) | a not in b | 3.1 |
assertIsInstance(a, b) | isinstance(a, b) | 3.2 |
assertNotIsInstance(a, b) | not isinstance(a, b) | 3.2 |
测试套件
测试套件就是测试用例的集合,unittest.TestSuite类实现了测试套件,使用测试套件,我们可以自由的组织测试用例。
添加测试用例
既然是容器,那么最重要的操作就是向容器中添加测试用例了,可以在构造的时候就指定,也可以通过addTest()方法。
# test可以是unittest.TestCase对象,或者unittest.TestSuite对象
class unittest.TestSuite(tests=())
# test可以是unittest.TestCase对象,或者unittest.TestSuite对象
addTest(test)
# tests可以是一个可迭代的unittest.TestCase对象集合,或者unittest.TestSuite对象
addTests(tests)
可以看出,unittest.TestSuite是可以嵌套的,此外unittest.TestSuite也实现了_iter_()魔法方法,所以它本身是一个可迭代对象,对它迭代会依次得到其中的测试用例。
测试用例加载器
如上,我们可以手动实例化unittest.TestSuite对象,然后调用其addTest()等方法向其中添加测试用例。但是unittest提供了更加便捷的unittest.TestLoader类来帮助我们从类和模块中创建unittest.TestSuite对象。
常见的一些方法如下:
# 加载一个测试类中的所有测试用例
loadTestsFromTestCase(testCaseClass)
# 加载一个模块中的所有测试用例
loadTestsFromModule(module, pattern=None)
测试执行者
测试用例组织成unittest.TestSuite后,需要执行者将其运行起来,unittest提供了文本格式的执行者unittest.TextTestRunner(其执行结果是文本格式的),它最重要的一个方法就是run()。
# test是一个unittest.TestCase对象或者unittest.TestSuite对象,返回unittest.TestResult对象来表示这些用例的执行结果
run(test)
用例执行结果
unittest.TestResult类记录了用例集的执行结果,对于想生成自定义测试报告的情景,可以从测试执行者中获取到该对象,具体见手册中的class unittest.TestResult 。
其它主题
- unittest支持从命令行执行测试用例,具体见手册的“Command-Line Interface”;
- 老测试用例整改。在老测试用例的基础上做少量修改后就可以复用unittest,具体见Re-using old test code;
- 如果想要跳过部分用例,或者期望用例的执行结果是失败的,那么可以使用unittest的skip修饰符,具体见Skipping tests and expected failures。个人理解该功能意义不大,为何不直接调整测试用例的逻辑,反而做这种修改;
- 如果一组用例逻辑非常相似,那么考虑使用subTest()来优化它们,这样可以避免冗余代码,具体见Distinguishing test iterations using subtests;
总结
对于unittest模块的使用,如果我们不想对用例的执行过程以及执行结果进行任何的定制,那么只需从unittest.TestCase派生一个子类,在其中实现用例方法,然后用unittest.main()函数触发所有用例的执行即可。
如果我们想要对用例的执行进行定制,那么就需要如下几步:
- 编写用例(从unittest.TestCase类派生子类);
- 组织测试用例(实例化unittest.TestSuite对象,可能会用到unittest.TestLoader);
- 执行用例(实例化unittest.TextTestRunner,或者unittest.main()方法);
- 获取用例执行结果(unittest.TestResult类,此时步骤三必须使用用例执行者)。