快速入门
本文章于 2023 年学习时记录,无法保证准确性
用例编写
unittest 是 Python 自带的一个单元测试框架
此外,unittest 还支持自动化测试的接口测试、UI 测试。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import unittest
class TestDemo(unittest.TestCase): def test_01_demo(self): self.assertEqual(1, 2, "判断1和2相等")
def test_02_demo(self): self.assertEqual(1, 1, "判断1和1相等")
if __name__ == '__main__': unittest.main()
|
使用 unittest 编写测试用例,必须使用测试类来进行。
在测试类里面的方法就是测试用例了。
注意,测试用例的方法必须是以test开头的才可以。
例如上述代码的test_01_demo
unittest.main()会自动的发现模块中的所有测试类中的测试用例。
在测试用例中的方法中,我们可以使用testCase自带的断言功能来实现对预期结果和实践
结果的判断。
TestSuite
我们可以使用测试套件,把不同的测试用例都加载添加到测试套件中
通过直接运行测试套件来实现运行不同模块中的测试用例
1 2 3 4 5
| import unittest
suite = unittest.TestSuite() case = unittest.defaultTestLoader.discover(start_dir='.', pattern="test*.py") suite.addTest(case)
|
上述代码中使用unittest.defaultTestLoader.discover方法就可以实现在指定的文件夹
中区自动的发现我们的测试用例
start_dir后面是测试模块所在的文件目录,如果在同一目录则是.
pattern参数后跟的是以 test 开头的 python 文件如”test01.py” “test02.py”,因此我
们文件要以有规律化的方式起名
最后使用suite.addTest的方法把发现的测试用例加载到测试套件中。
运行套件以及生成测试报告
unittest 自带的测试报告是文本格式的,可读性不好,所以我们一般用第三方的模块来生
成测试报告
1 2 3 4 5 6 7 8 9 10 11 12
| import unittest from HTMLTestRunner import HTMLTestRunner
suite = unittest.TestSuite() case = unittest.defaultTestLoader.discover(start_dir='.', pattern="test*.py")
suite.addTest(case)
with open("测试报告.html", "wb")as f: runner = HTMLTestRunner(stream=f, title="测试报告", description="这是项目的描述") runner.run(suite)
|
HTMLTestRunner是我们经常使用的第三方模块,可以将生成的测试报告以 HTML 方式展现
。其中:
stream是要把测试报告的文件保存到那个文件目录中
title是测试报告的标题
description是对测试报告的详细描述
assert断言
常用的断言
assertEqual判断两个值是否相等
assertNotEqual判断两个值是否不相等
assertTrue判断结果是否为真
assertFalse判断结果是否为假
assertIn判断第一个值是否在第二个值范围内
assertNotIn判断第一个值是否不在第二个值的范围内
assertIs判断两个对象是否属于同一对象
assertIsNot判断两个对象是否不属于同一种对象
assertIsNone判断某个值是否为空
assertIsNotNone判断某个是是否不为空
assertIsInstance判断某个对象是否属于某个 class 实例
assertNotIsInstance判断某个对象是否不属于某个 class 实例
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import unittest
class Demo: pass
class Demo2: pass
aa = Demo a = 1 b = a c = None
class TestDemo(unittest.TestCase): def test_01_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 2, "判断两个值是否不相等") self.assertTrue(1 == 1, "判断结果是否为真") self.assertFalse(1 == 2, "判断结果是否为假") self.assertIn(1, [2, 1, 3], "判断第一个值是否在第二个容器内") self.assertNotIn(1, [3, 2, 3], "判断第一个值是否不在第二个容器内") self.assertIs(a, b, "判断任意两个对象是否属于同一个对象") self.assertIsNot(a, 3, "判断任意两个对象是否不属于同一个对象") self.assertIsNone(c, "判断某个值是否为空值") self.assertIsNotNone(a, "判断某个值是否不为空") self.assertIsInstance(aa, Demo, "判断某个对象是否属于某个class实例") self.assertNotIsInstance(aa, Demo2, "判断某个对象是否不属于某个class实例")
if __name__ == '__main__': unittest.main()
|
unittest装饰器
testCase带有很多不同的装饰器来实现我们测试用例的运行过程。
但是在实际工作中会有大量的测试用例,但不是所有的用例都是需要运行的。
有的用例需要根据不同的情况来判断是否需要运行。
我们可以利用testCase自带的装饰器来实现用例运行过程的控制。
装饰器
@unittest.skip() 控制某条用例不运行
@unittest.skipif() 如果条件为真,用例不运行
@unittest.skipUnless()如果条件为假,用例不运行
@unittest.expectedFailure预期测试失败,失败时输出x成功时输出u
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class TestDemo(unittest.TestCase): def test_01_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 2, "判断两个值是否不相等")
def test_02_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 1, "判断两个值是否不相等")
@unittest.skip("跳过第三条用例") def test_03_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 2, "判断两个值是否不相等")
@unittest.skipIf(True, "结果为真,跳过用例") def test_04_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 2, "判断两个值是否不相等")
@unittest.skipUnless(False, "结果为假,跳过用例") def test_05_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 2, "判断两个值是否不相等")
@unittest.expectedFailure def test_06_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 1, "判断两个值是否不相等")
@unittest.expectedFailure def test_07_demo(self): self.assertEqual(1, 1, "判断两个值是否相等") self.assertNotEqual(1, 2, "判断两个值是否不相等")
|
控制台结果:
1 2 3 4
| .Fsssxu ====================================================================== FAIL: test_02_demo (__main__.TestDemo.test_02_demo) ----------------------------------------------------------------------
|
运行结果的说明
.表示测试通过
F表示测试失败
s表示测试跳过
x表示预期失败
u表示与预期相反
fixture测试夹具
以初始化的夹具在前,测试运行结束后的夹具在后运行测试用例。
测试夹具的作用可以把我们在测试运行前和运行结束后需要运行的代码抽离出来,单独放到
夹具中区运行。方便我们对代码的组织和维护,减少重复的代码量。
setUp以及setDown是面向测试用例
setUpClass以及tearDownClass是面向测试类
两者可以组合使用同时出现
setUp
在每个测试用例前运行,一般在里面写一些实现测试用例的前置条件
tearDown
在每个测试用例运行结束后运行,一般在里面写一些清理环境,释放资源之类的代码
setUpClass
在每个测试类运行前运行 代码前需要带上@classmethod 装饰器
tearDownClass
在每个测试类结束运行之后运行 代码前需要带上@classmethod 装饰器
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class TestDemo(unittest.TestCase): def setUp(self) -> None: print("开始运行测试用例")
def tearDown(self) -> None: print("测试用例运行结束")
@classmethod def setUpClass(cls) -> None: print("开始运行测试类")
@classmethod def tearDownClass(cls) -> None: print("测试类结束运行")
def test_01_demo(self): self.assertEqual(1, 1, "判断是否相等")
def test_02_demo(self): self.assertEqual(1, 1, "判断是否相等")
def test_03_demo(self): self.assertEqual(1, 1, "判断是否相等")
|
清理函数
清理函数默认在tearDown后运行
清理函数的使用,需要先自己封装一个方法,这个方法里面的的代码就是用来清理的
清理函数可以放在测试类中的任何位置,通过addCleanup()注册我们的清理函数
清理函数可以使用doCleanups()对某个用例生效,也可放在setUp中对所有用例生效
在 python3.9 之后新增了addClassCleanup以及doClassCleanup,他们是针对测试类进
行清理,用法同上。
addClassCleanup一般与setClassUp搭配使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import unittest
def clear(): print("开始清理函数")
class TestDemo(unittest.TestCase): def setUp(self) -> None: self.addCleanup(clear) print("开始运行测试") def tearDown(self) -> None: print("测试运行结束") def test_01_demo(self): self.assertEqual(1, 1, "判断是否相等") def test_02_demo(self): self.assertEqual(1, 1, "判断是否相等") def test_03_demo(self): slf.assertEqual(1, 1, "判断是否相等") def test_04_demo(self): self.assertEqual(1, 1, "判断是否相等") def test_05_demo(self): self.assertEqual(1, 1, "判断是否相等")
if __name__ == '__main__': unittest.main()
|
注意:清理函数不是必须要和夹具配置使用,也可以单独使用。
ddt 数据驱动
介绍
ddt 是 python 的一个专门从来实现数据驱动的第三方的包
使用 ddt 数据驱动来实现数据与测试分离管理
1 2
| # 使用pip下载 pip install ddt -i https://pypi.douban.com/simple
|
下载后即可通过导包来使用 ddt
1 2
| form ddt import ddt, data, unpack, file_data
|
各项装饰器的使用
ddt
在测试用例前加上@ddt装饰器即可使用 ddt 数据驱动
1 2
| @ddt class TestDemo(unittest.TestCase):
|
data
需要进行测试的数据,放在 data 中。在测试中直接引用 value 即可
1 2 3 4 5 6 7 8 9 10 11
| from ddt import ddt, data, unpack, file_data import unittest
@ddt class TestDemo(unittest.TestCase): @data(1, 2, 3) def test_01_demo(self, value): self.assertEqual(1, value, "判断是否相等")
if __name__ == '__main__': unittest.main()
|
file_data
可以实现自动的从文件中读取测试数据,不需要像 data 一样手动传入数据。
可以使用 json 或者 yaml
例如在同目录下有名为 data.json 数据文件时
1 2 3 4 5
| { "01": { "name": "张三", "age": 23 }, "02": { "name": "李四", "age": 24 }, "03": { "name": "王五", "age": 25 } }
|
在测试时可以用@file_data 直接引用
1 2 3 4 5 6 7 8 9 10 11 12
| from ddt import ddt, data, unpack, file_data import unittest
@ddt class TestDemo(unittest.TestCase): @file_data("data.json") @unpack def test_05_demo(self, age, name): self.assertEqual(23, age, name)
if __name__ == '__main__': unittest.main()
|
同理在引用 yaml 文件时操作相同,但注意:
ddt 是直接支持 json 格式文件的读取,但是对于 yaml 文件需要额外安装一个第三方的
python 包才可以。pyyaml
1
| pip install pyyaml -i https://douban.com/simple
|
unpack
对数据进行解包,特别在每项数据都是成组出现时
例如数据内为元组时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from ddt import ddt, data, unpack, file_data import unittest
@ddt class TestDemo(unittest.TestCase): @data((1, 2), (2, 2), (3, 4)) def test_01_demo(self, value): self.assertEqual(value[0], value[1], "判断是否相等") @data((1, 2), (2, 2), (3, 4)) @unpack def test_02_demo(self,value1, value2): self.assertEqual(value1, value2, "判断是否相等")
if __name__ == '__main__': unittest.main()
|
例如数据内为字典时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from ddt import ddt, data, unpack, file_data import unittest
@ddt class TestDemo(unittest.TestCase):
@data({"name": "张三", "age": 23}, {"name": "李四", "age": 24}, {"name": "王五", "age": 25}) @unpack def test_01_demo(self, name, age): self.assertEqual(23, age, name)
if __name__ == '__main__': unittest.main()
|