使用Unittest框架运行测试用例
- [Unittest 框架](#Unittest 框架)
-
- [1.Unittest 单元测试框架的组成](#1.Unittest 单元测试框架的组成)
- [2.TestCase 编写测试用例](#2.TestCase 编写测试用例)
- 3.跳过要执行的测试用例
- 4.引入TestFixture
-
- [@classmethod 装饰器](#@classmethod 装饰器)
- 5.Unittest的断言
- 6.TestSuite和TestRunner(不支持并发结果合并)
-
- [1)运行时自动触发unittest.main()逻辑,需修改 PyCharm 配置](#1)运行时自动触发unittest.main()逻辑,需修改 PyCharm 配置)
- 2)TestSuite添加测试用例的3种方式
- 3)TestLoader添加测试用例
- [7. 生成测试报告TestResult](#7. 生成测试报告TestResult)
- [8. 框架进行并发(unittest不适合,使用pytest)](#8. 框架进行并发(unittest不适合,使用pytest))
Unittest 框架
作用: 管理测试用例、断言、生成测试报告
1.Unittest 单元测试框架的组成
Unittest 组成部分 | 作用 |
---|---|
test fixture | 测试固定组件,unittest框架中,一些有固定用法的组件 |
test case | 测试用例,被执行测试的最小单元 |
test suite | 测试套件,它是一个用例集,用来汇总应该一起执行的测试用例 |
test runner | 测试运行器,它是一个设计测试执行方式的元件,主要对用户提供了输出结果的展现方式。它可以用图标、文本、html等方式来展现测试结果 |
2.TestCase 编写测试用例
- 简单执行一个用例 import unittest
Unittest运行的测试用例,必须是继承了 unittest.TestCase 的方法,并且这个方法默认以 test 开头
默认执行顺序是,在test以后,按照ASCII码比对大小
python
---- 导入包 unittest
import unittest
---- 创建继承 unittest.TestCase 的类
class FirstDemo(unittest.TestCase): ------- 固定写法,必须继承
def test01(self):
print("执行用例一")
def test02(self):
print("执行用例二")
if __name__ == '__main__':
unittest.main() ----- 固定写法,调用底层逻辑main执行用例,查看下图

- 执行结果(左侧为执行情况,右侧为执行输出和执行时间)

- 若想更换执行时的测试运行程序

3.跳过要执行的测试用例
方法:使用装饰器 @unitest.skip("备注")
- 案例
python
---- 导入包 unittest
import unittest
---- 创建继承 unittest.TestCase 的类
class FirstDemo(unittest.TestCase): ------- 固定写法,必须继承
def test01(self):
print("执行用例一")
@unittest.skip("跳过用例二") ------ @unitest.skip
def test02(self):
print("执行用例二")
if __name__ == '__main__':
unittest.main()
- 运行结果:跳过的内容会被标注

4.引入TestFixture
TestFixture一般都是写在运行的测试用例的类中,作用该类的类方法或者实例方法而存在
运行顺序:setUpClass() - - - 反复执行{ setUp()、测试用例test、tearDown() } - - - tearDownClass()|
TestFixture常用方法 | 应用场景 |
---|---|
setUp() | 每运行一个测试用例之前,先运行的函数。主要用于设置一些配置信息,静态属性等 |
setUpClass() | 类方法,必须和@classmethod装饰器结合使用,实例化类后,会自动运行的方法主要用于实例化类、设置某些环境配置如数据库连接配置等 |
tearDown() | 每运行完一个测试用例之后,后运行的函数。主要用于销毁每个测试用例之间的数据,释放资源,还原数据 |
tearDownClass() | 类中的代码全部运行完成后,会自动运行的方法,必须和@classmethod装饰器结合使用。主要用于销毁类级别的资源,还原数据 |
@classmethod 装饰器
类方法:被 @classmethod 装饰的函数,可以不用实例化类,就能够访问呢
举例:class A: pass - - - 实例化: A().aa - - - 不实例化: A.bb
在 unittest 当中 @classmethod 是 setUpClass() 和 tearDownClass() 的固定写法
- 案例:测试加法运算
python
# 导入unittest模块
import unittest
# 创建要执行的测试类
class add_doing:
def Calculator(self,x,y):
return x + y
# 创建unittest的测试类
class TestPlus(unittest.TestCase):
add = None
# 编写测试固件
def setUp(self): # 如果不想写内容可以写pass跳过
self.x = 1
self.y = 1
print("运行了setUp")
@classmethod
def setUpClass(cls):
cls.add = add_doing()
print("运行了setUpClass")
def tearDown(self): # 和setUp一一对应
del self.x
del self.y
print("运行了tearDown")
@classmethod
def tearDownClass(cls): # 和setUpClass一一对应
del cls.add
print("运行了tearDownClass")
# 测试用例,测试要执行的测试类
def test01(self):
print(self.add.Calculator(2,4))
def test02(self):
print(self.add.Calculator(5,6))
def test03(self):
print(self.add.Calculator(self.x,self.y))
if __name__ == '__main__':
unittest.main()
- 运行结果

5.Unittest的断言
Unittest断言方法 | 作用 |
---|---|
assertEqual(a,b) | 检査 a和b是否相等 |
assertTrue(x) | 检查x是不是一个True |
assertls(a,b) | 检查a和b是不是完全一样 |
assertlsNone(x) | 检查x是不是一个None |
assertIn(a,b) | 检查a是不是b的子集 |
assertlsInstance(a,b) | 检查a、b两个对象,实例类型是否相同 |
- 案例
python
import unittest
class TestAssert(unittest.TestCase):
----- unittest的断言是在TestCase中实现的,所以必须继承TestCase之后,才能够使用unittest的断言方法
def setUp(self):
self.l1,self.l2 = [1,2],[1,2]
self.a = 1
self.b = 1
def test01_assertEqual(self):
----- 断言self.a和self.b是否完全相等
self.assertEqual(self.a, self.b)
self.assertEqual(self.l1, self.l2)
def test02_assertTrue(self):
----- assertTrue(x)可以根据传入的数据,判断真或者假(True,False)
# assertTrue(0)是False
# assertTrue(None)是False
# assertTrue(1)是True
self.assertTrue(self.a)
self.assertTrue(self.l1)
def test03_assertIs(self):
----- 断言self.a和self.b是否完全相同
self.assertIs(self.a, self.b)
self.assertIs(self.l1, self.l2) # False
def test04_assertIn(self):
----- 断言a是否是 l1 的子集
self.assertIn(self.a, self.l1) # True
self.assertIn(self.l1, self.l2) ----- False 列表不能做子集运算
def test05_assertIsNone(self):
----- 断言是不是None
self.assertIsNone(self.a) # False
self.assertIsNone(None) # True
def test06_assertIsInstance(self):
----- 断言类型
self.assertIsInstance(self.a, int) # 判断a是不是整形 True
self.assertIsInstance(self.l1, int) # False
6.TestSuite和TestRunner(不支持并发结果合并)
test suite | 测试套件,它是一个用例集,用来汇总应该一起执行的测试用例 |
test runner | 测试运行器,它是一个设计测试执行方式的元件,主要对用户的输出结果进行展示 |
- 案例
python
# 导包
import unittest
# 创建要执行的测试用例类
class TestSuiteDemo(unittest.TestCase):
def test01(self):
print("执行的测试用例test01,a")
def test02(self):
print("执行的测试用例test02,b")
class TestSuiteDemo2(unittest.TestCase):
def test01(self):
print("执行的测试用例test01,张三")
def test02(self):
print("执行的测试用例test02,李四")
if __name__ == '__main__':
# 实例化测试套件
suite = unittest.TestSuite()
# 将测试用例添加到测试套件中
suite.addTest(TestSuiteDemo('test01'))
suite.addTest(TestSuiteDemo2('test02'))
# 实例化runner
runner = unittest.TextTestRunner()
# 使用runner运行测试用例生成测试报告
runner.run(suite)
1)运行时自动触发unittest.main()逻辑,需修改 PyCharm 配置

- 修改配置后的运行结果

2)TestSuite添加测试用例的3种方式
TestSuite添加测试用例的3种方式 | 写法举例(括号内命名可变) |
---|---|
suite.addTest 单个添加 | suite.addTest(TestSuiteDemo('test01')) |
suite.addTests 多个添加 | suie.addTests([TestSuiteDemo('test01'),TestSuiteDemo2('test02')]) |
TestLoader | 下面会重点介绍 |
3)TestLoader添加测试用例
两种方法 | 解释 |
---|---|
TestLoader().discover("./","*.py") | 根据执行的路径来寻找指定py文件中的测试用例,加载到测试套件当中 |
TestLoader().loadTestsFromTestCase(测试用例类名) | 根据测试用例的类名来加载测试用例 |
- 案例
python
# 导入模块
import unittest
# 创建测试用例的类
class TestSuiteDemo1(unittest.TestCase):
def test01(self):
print("执行的测试用例test01,a")
def test02(self):
print("执行的测试用例test02,b")
class TestSuiteDemo2(unittest.TestCase):
def test01(self):
print("执行的测试用例test01,张三")
def test02(self):
print("执行的测试用例test02,李四")
class TestSuiteDemo3(unittest.TestCase):
def test01(self):
print("单元测试")
def test02(self):
print("集成测试")
def test03(self):
print("系统测试")
- 方法一:TestLoader().discover("./","*.py")
python
if __name__ == '__main__':
------ 实例化testloaders
tl = unittest.TestLoader()
--- 使用testloader的对象tl来加载测试用例成测试套件
suite = tl.discover('./','four_unittest.py')
runner = unittest.TextTestRunner()
runner.run(suite)
- 运行结果

- 方法二:TestLoader().loadTestsFromTestCase(测试用例类名)
python
if __name__ == '__main__':
# 实例化testloaders
tl = unittest.TestLoader()
# 使用testloader的对象tl来加载测试用例成测试套件
suite = tl.loadTestsFromTestCase(TestSuiteDemo3)
runner = unittest.TextTestRunner()
runner.run(suite)
- 运行结果

7. 生成测试报告TestResult
使用unittest运行测试用例,最后会生成测试报告,我们可以把测试报告保存到文件中
1)TextTestRunner生成的文本格式报告
- 查看TextTestRunner底层代码

- 案例
python
if __name__ == '__main__':
tl = unittest.TestLoader()
suite = tl.discover('./','four_unittest.py')
--- 使用TextTestRunner生成测试报告
--- 1.打开要保存的测试报告文件,'w'是写入的意思
with open('./result.txt',mode='w') as f:
--- 2.实例化TextTestRunner
runner = unittest.TextTestRunner(f, verbosity=3,descriptions=True)
--- 3.使用runner运行测试套件
runner.run(suite)
- 运行结果
2)HTMLTestRunner_PY3生成的报告(好用,需安装)
HTMLTestRunner_Py3是一个能生成HTML格式的,显示更友好的测试报告,它不仅能够显示出测试用例的执行结果,还能跟踪测试用例执行失败的原因
- 安装成功结果显示

- 案例
python
from Scripts.HTMLTestRunner import HTMLTestRunner
if __name__ == '__main__':
tl = unittest.TestLoader()
suite = tl.discover('./','four_unittest.py')
--- 使用HTMLTestRunner生成测试报告
--- 1.打开要保存的测试报告文件,'wb'是写入的意思
with open('./result.html',mode='wb') as f:
--- 2.实例化HTMLTestRunner
runner = HTMLTestRunner(f,verbosity=2,title='演示HTML测试报告')
--- 3.使用runner运行测试套件
runner.run(suite)
- 运行结果

- 浏览器展示

8. 框架进行并发(unittest不适合,使用pytest)
使用多线程运行,可以并行执行测试用例,提升测试速度
框架 | 是否推荐多线程 | 推荐方式 |
---|---|---|
unittest | 不推荐手动用 threading 或 threadpool | 使用 concurrent.futures + 手动结果汇总,或改用 pytest |
pytest | 强烈推荐使用并发执行 | 使用 pytest-xdist 插件(基于多进程,非多线程) |
- unittest使用concurrent.futures多线程举例(适用于老项目)
python
from concurrent.futures import ThreadPoolExecutor
import unittest
def run_test_case(test):
result = unittest.TestResult()
test(result)
return result
if __name__ == '__main__':
# 拆分测试用例
suite = unittest.TestLoader().discover('./','*.py')
test_cases = [test for test in suite]
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(run_test_case, test) for test in test_cases]
# 汇总结果
total_result = unittest.TestResult()
for future in futures:
result = future.result()
# 手动合并结果(注意线程安全)
total_result.errors.extend(result.errors)
total_result.failures.extend(result.failures)
total_result.testsRun += result.testsRun