Unittest 单元测试框架(一)
1. 单元测试
1.1 什么是单元测试?
单元测试是一种软件测试方法,主要用于验证软件的最小可测试单元(通常是函数、方法或类)是否按预期工作。
最小可测单元:函数、方法、类、文件、模块
1.2 单元测试落地挑战
- 不知道怎么编写单元测试
- 结对测试:由测试设计提供单元测试用例,由开发编写实现单元测试代码
- 公司、团队、项目没有要求
- 寻找工作痛点、收集数据、分析问题原因、提出解决方案
- 单元测试价值不高,写了也是浪费时间
1.3 单元测试怎么做?
首先,需要结合你的团队成员的技能、意愿、当前问题、项目迭代周期等等。
任何体能增销售端一定是要建立在合适的时机来切入,不能一味的强推!
- 试点(找一两个团队试试水,取得效果后再全面推广)
- 要有优先级,从 P0 核心模板开始
- 要有度量,需要衡量你做这件事的价值
2. Unittest 基础
2.1 框架介绍
官方文档:https://docs.python.org/zh-cn/3/library/unittest.html
Unittest 是 Python 标准库中自带的单元测试框架,Unittest 也被称为 PyUnit,就像 JUnit 是 Java 语言的标准单元测试框架一样,Unittest 则是 Python 语言的标准单元测试框架。
Unittest 单元测试框架不仅可以适用于 Python 项目的单元测试,还可以适用接口测试、Web 自动化测试等,它有一个很重要的特性:它是通过类(class)的方式,将测试用例组织在一起。
2.2 核心要素及常用属性
核心要素
Unittest 框架中最核心的四个概念是:test case、testsuite、test runner 和 test fixture。
- 一个 TestCase 的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
- 而多个测试用例集合在一起,就是 TestSuite,而且 TestSuite 也可以嵌套 TestSuite。
- TestLoader 是用来加载 TestCase 到 TestSuite 中的,其中有几个 loadTestsFrom__() 方法,就是从各个地方寻找 TestCase,创建它们的实例,然后 add 到 TestSuite 中,再返回一个 TestSuite 实例。
- TextTestRunner 是来执行测试用例的,其中的 run(test) 会执行 TestSuite / TestCase 中的 run(result) 方法。
- 测试的结果会保存到 TextTestResult 实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
- 而对一个测试用例环境的搭建和销毁,是一个 fixture。
这样整个流程就清楚了,首先是要写好 TestCase,然后由 TestLoader 加载 TestCase 到 TestSuite,然后由 TextTestRunner 来运行 TestSuite,运行的结果保存在 TextTestResult 中,整个过程集成在 unittest.main 模块中。
常用属性
uniitest 的属性如下:
说明:
- unittest.TestCase:TestCase 类,所有测试用例类继承的基类。
- unittest.TestSuite():unittest 框架的 TestSuite() 类是用来创建测试套件的。
- unittest.main():将一个单元测试模块变为可直接运行的测试脚本,main() 方法使用 TestLoader 类来搜索所有包含在该模块中以 “test” 命名开头的测试方法,并自动执行他们,根据 ASCII 码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。
- unittest.TextTestRunner():unittest 框架的 TextTestRunner() 类,通过该类下面的 run() 方法来运行 suite 所组装的测试用例,入参为 suite 测试套件。
2.3 基本使用
我们用一个基础的计算器的例子来看看 unittest 是如何使用的。
1 | # 计算器类 |
我们需要单独创建一个写单元测试的文件出来,并且编写步骤如下:
- 导入 unittest 模块、被测文件或者其中的类
- 创建一个测试类,并继承 unittest.TestCase
- 重写 setUp 和 tearDown 方法(可选,如果有初始化条件和结束条件)
- 定义测试函数,函数以 test 开头
- 在函数体中使用断言来判断测试结果是否符合预期结果
- 调用 unittest.main() 方法来运行测试用例
1 | # !/usr/bin/python3 |
这里执行时需要注意的一个点是,执行测试用例的顺序不是按照文件中定义的顺序,而是根据 ASCII 码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。
如果想按照指定的顺序来执行用例,则可以在用例名称中增加一个数字的标识
1 | def test_001_add(self):... |
3. Unittest 常见用法
3.1 TestSuite 测试套件
3.1.1 加载单个用例
除了上面基本使用里的方法可以执行测试用例,我们同样也可以使用 TestSuite 测试套件来执行测试用例。
1 | # !/usr/bin/python3 |
3.1.2 加载多个类
上面这种方式添加测试用例会比较麻烦,当用例很多的时候显然一个个添加就不太合适了。
因此可以通过 unittest.TestLoader().loadTestsFromTestCase(类名) 可以一次加载某个类下面的所有测试用例,也支持加载多个类。
1 | import unittest |
3.1.3 匹配目录下的用例
假如某个目录下存在多个测试模块,test1.py 和 test2.py、test3.py,如果用 addTests 的方法添加用例到测试套件,未免有点麻烦,这时可以用到 TestLoader() 这个类。
1 | import os |
3.2 保存用例执行结果
如果想将用例执行的结果持久化的保存下来,则可以通过下面的方法将结果写入到一个文件中。
1 | if __name__ == '__main__': |
Tips:
如果想要执行的结果更加详细,则可以指定 verbosity 属性为 2。
3.3 setUp() 和 tearDown()
setUp() 和 tearDown() 这两个方法在每个测试方法执行前以及执行后执行一次,setUp 用来为测试准备环境,tearDown 用来清理环境。
如果想要在一个类的所有 case 执行之前准备一次环境,并在所有 case 执行纪结束之后再清理环境,我们可以用 setUpClass() 与 tearDownClass()。
3.4 跳过某个 Case
如果我们临时想要跳过某个 case 不执行怎么办?unittest 也提供了两种方法:
- skip 装饰器
@unittest.skip(reason):skip(reason)装饰器无条件跳过装饰的测试,并说明跳过测试的原因。@unittest.skiplf(reason):skiplf(condition, reason)装饰器条件为真时,跳过装饰的测试,并说明跳过测试的原因。@unittest.skipUnless(reason):skipUnless(condition, reason)装饰器条件为假时,跳过装饰的测试,并说明跳过测试的原因。
1 | # !/usr/bin/python3 |
- TestCase.skipTest() 方法
1 | def test_multip(self): |
3.5 断言方法
一般接口测试断言需要关注的地方:
- HTTP 状态码的验证:查看状态返回是否是 200,即 HTTP 连接是成功的
- 业务状态码的验证:当前业务下该接口调用返回的值是否是正常的,例如返回 status = 0 表示正常
- 数据的验证:对返回的数据进行断言,验证数据是否符合预期
- 性能的验证:一个接口的响应时间不能过长
| 断言 | 作用 |
|---|---|
assertEqual(a, b, [msg='失败时打印的信息') |
断言 a,b 是否相等,相等则用例通过。 |
assertNotEqual(a, b, [msg='失败时打印的信息') |
断言 a,b 是否相等, 不相等则试用例通过 |
assertTrue(x, [msg='失败时打印的信息']) |
断言 x 是否 True,是 True 则用例通过。 |
assertFalse(x, [msg='失败时打印的信息') |
断言 x 是否 False,是 False 则用例通过。 |
assertIs(a, b, [msg='失败时打印的信息']) |
断言 a 是否是 b,是则用例通过。 |
assertNotIs(a, b, [msg='失败时打印的信息') |
断言 a 是否是 b,不是则用例通过。 |
assertlsNone(x, [msg='失败时打印的信息']) |
断言 x 是否 None,是 None,是 None 则用例通过。 |
assertIsNotNone(x, [msg='失败时打印的信息]) |
断言 x 是否 None,不是 None 则用例通过。 |
assertln(a, b, [msg='失败时打印的信息') |
断言 a 是否在 b 中,在 b 中则用例通过。。 |
assertNotIn(a, b, [msg='失败时打印的信息']) |
断言 a 是否在 b 中,不在 b 中则用例通过 |
assertIsInstance(a, b, [msg='失败时打印的信息') |
断言 a 是 b 的一个实例,是则用例通过。 |
assertNotIsInstance(a, b, [msg='失败打印的信息']) |
断言 a 不是 b 的一个实例,不是则用例过。 |
3.6 命令行执行用例方式
unittest 支持执行指定用例:
- 指定测试模块
1 | python -m unittest test_module1 test_module2 |
- 指定测试类
1 | python -m unittest test_module.TestClass |
- 指定测试方法
1 | python -m unittest test_module.TestClass.test_method |
unittest 支持用例自动(递归)发现:
默认发现当前目录下所有符合 test*.py 测试用例
使用
python -m unittest或python -m unittest discover通过 -s 参数指定要自动发现的目录,-p 参数指定用例文件的名称模式
1 | python -m unittest discover -s project_directory -p "test_*.py" |






