文章目录

  • 概述
  • 测试用例
  • 测试夹具
  • 断言
  • 测试套件
  • 添加测试用例
  • 测试用例加载器
  • 测试执行者
  • 用例执行结果
  • 其它主题
  • 总结

这篇笔记记录了Python内建模块unittest的知识点,所记录的内容均来自unittest的官方文档。

概述

unittest是Python标准库中的一个模块,使用它可以对Python代码进行单元测试,它支持如下特性:

  1. 测试自动化;
  2. 共享测试夹具(即测试用例的setUp()和tearDown()代码);
  3. 将测试用例组织成集合;
  4. 测试用例和测试报告框架之间相独立;

为了支持上述特性,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

其它主题

  1. unittest支持从命令行执行测试用例,具体见手册的“Command-Line Interface”
  2. 老测试用例整改。在老测试用例的基础上做少量修改后就可以复用unittest,具体见Re-using old test code
  3. 如果想要跳过部分用例,或者期望用例的执行结果是失败的,那么可以使用unittest的skip修饰符,具体见Skipping tests and expected failures。个人理解该功能意义不大,为何不直接调整测试用例的逻辑,反而做这种修改;
  4. 如果一组用例逻辑非常相似,那么考虑使用subTest()来优化它们,这样可以避免冗余代码,具体见Distinguishing test iterations using subtests

总结

对于unittest模块的使用,如果我们不想对用例的执行过程以及执行结果进行任何的定制,那么只需从unittest.TestCase派生一个子类,在其中实现用例方法,然后用unittest.main()函数触发所有用例的执行即可。

如果我们想要对用例的执行进行定制,那么就需要如下几步:

  1. 编写用例(从unittest.TestCase类派生子类);
  2. 组织测试用例(实例化unittest.TestSuite对象,可能会用到unittest.TestLoader);
  3. 执行用例(实例化unittest.TextTestRunner,或者unittest.main()方法);
  4. 获取用例执行结果(unittest.TestResult类,此时步骤三必须使用用例执行者)。