Test
发表于:2025-06-10 | 分类: Python
字数统计: 1.7k | 阅读时长: 7分钟 | 阅读量:

常用的python测试框架,推荐使用 pytest

Test

unittest

内置标准库。支持测试自动化。提供了一系列创建和运行测试的工具。

所有测试文件都必须是可从项目的最高层级目录导入的 模块 或 包。

概念

  • test fixture: 测试脚手架,测试所需要进行的准备工作及所有相关的清理操作。
  • test case: 测试用例,检查输入特定的数据时的响应。TestCase 类用于新建测试用例。
  • test suite: 测试套件,一系列的测试用例,或测试套件,用于归档需要一起执行的测试。
  • test runner: 用于执行和输出测试结果的组件。可能使用图形接口、文本接口,或返回一个特定的值表示运行测试的结果。

最佳实践

  • 测试方法以 test 开头。
  • 使用 assertEqual() assertTrue() assertFalse() assertRaises()等来验证结果或异常,以便生成测试报告。
  • 使用 setup() tearDown() 设置测试开始前与完成后需要执行的指令。
  • 测试代码放在单独的模块中,具有测试模块可单独命令行调用,分发时更容易剥离等优点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import unittest

class TestStringMethods(unittest.TestCase):

def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')

def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertFalse('Foo'.isupper())

def test_split(self):
s = 'hello world'
self.assertEqual(s.split(), ['hello', 'world'])
# 检查当分隔符不为字符串时 s.split 是否失败
with self.assertRaises(TypeError):
s.split(2)

if __name__ == '__main__':
unittest.main()

命令行接口

1
2
3
4
5
6
7
8
9
10
# 测试整个模块
python -m unittest test_module1 test_module2
# 测试整个类
python -m unittest test_module.TestClass
# 测试类中方法
python -m unittest test_module.TestClass.test_method
# 测试指定文件
python -m unittest tests/test_something.py
# -v: 输出详细测试信息
python -m unittest -v test_module

命令行选项

  • -v: –verbose, 输出详细测试信息
  • -f: –failfast, 出现错误或者失败时,提前停止运行测试
  • -s: –start-directory, 开始搜索的目录, 默认当前目录
  • -p: –pattern, 正则匹配测试文件名,默认test*.py

组织测试代码

多个测试运行的顺序由内置字符串排序方法对测试名进行排序的结果决定。

自定义测试子类,这种以 TestCase 测试代码运行的环境被称为 test fixture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import unittest

# 若测试不通过,将会引发异常并附带解释性信息,并标记为 failure
class WidgetTestCase(unittest.TestCase):
# 测试前的前置准备,若发生异常则认为测试发生了错误,测试方法不会被运行。
def setUp(self):
self.widget = Widget('The widget')

# 测试完成后的清理操作,无论测试结果是否成功
def tearDown(self):
self.widget.dispose()

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')

断言方法:

  • 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
  • assertIsNot(a, b) : a not is b
  • assertIsNone(x) : x is None
  • assertIsNotNone(x) : x is not None
  • assertIn(a, b) : a in b
  • assertNotIn(a, b) : a not in b
  • assertIsInstance(a, b) : isinstance(a, b)
  • assertNotIsInstance(a, b) : not isinstance(a, b)
  • assertAlmostEqual(a, b) : round(a-b, 7) == 0
  • assertNotAlmostEqual(a, b) : round(a-b, 7) != 0
  • assertGreater(a, b) : a > b
  • assertGreaterEqual(a, b) : a >= b
  • assertLess(a, b) : a < b
  • assertLessEqual(a, b) : a <= b
  • assertRegex(s, r) : r.search(s)
  • assertNotRegex(s, r) : not r.search(s)
  • assertCountEqual(a, b) : 检查每个元素在两个序列中的出现次数是否相同。

assertEqual(a, b) 的默认比较方法,也可自定义:

  • assertMultiLineEqual(a, b) : 比较字符串
  • assertSequenceEqual(a, b) : 比较序列
  • assertListEqual(a, b) : 比较列表
  • assertTupleEqual(a, b) : 比较元组
  • assertSetEqual(a, b) : 比较集合
  • assertDictEqual(a, b) : 比较字典
  • addTypeEqualityFunc(typeobj, function) : 自定义指定类型的比较方法

自定义测试套件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase('test_default_widget_size'))
suite.addTest(WidgetTestCase('test_widget_resize'))
return suite

if __name__ == '__main__':
runner = unittest.TextTestRunner()
# 返回测试对象所提供的测试数量,包括单独的测试和子套件
runner.countTestCases(suite())
# 运行测试,但不收集结果
runner.debug(suite())
# 运行测试,并收集结果
runner.run(suite())

复用测试代码

用户有时希望直接使用 unittest 运行已有的测试代码,而不需要把已有的每个测试函数转化为一个 TestCase 的子类。

不推荐,继承 TestCase 的方式会让以后重构测试更加轻松。

1
2
3
4
5
6
7
8
9
def testSomething():
something = makeSomething()
assert something.name is not None
# ...

# 支持前置和后置方法
testcase = unittest.FunctionTestCase(testSomething,
setUp=makeSomethingDB,
tearDown=deleteSomethingDB)

跳过测试&预计失败

支持跳过单个或整组的测试用例、支持把测试标注成“预期失败”的测试(会失败但不计入 TestResult 中)。

  • @unittest.skip(reason): 跳过被此装饰器装饰的测试。 reason 为跳过原因。
  • @unittest.skipIf(condition, reason): condition=True时跳过。
  • @unittest.skipUnless(condition, reason): 跳过测试,除非 condition=True
  • @unittest.expectedFailure: 将测试标记为预期的失败或错误。发生错误则表示测试成功。
  • exception unittest.SkipTest(reason): 引发此异常以跳过一个测试。不推荐直接引发此异常。推荐用以上装饰器。

被跳过的测试的 setUp()tearDown() 不会被运行。被跳过的类的 setUpClass()tearDownClass() 不会被运行。被跳过的模组的 setUpModule()tearDownModule() 不会被运行。

跳过测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyTestCase(unittest.TestCase):
@unittest.skip("demonstrating skipping")
def test_nothing(self):
self.fail("shouldn't happen")

@unittest.skipIf(mylib.__version__ < (1, 3), "not supported in this library version")
def test_format(self):
# 测试其是否仅适用于特定的库版本。
pass

@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_windows_support(self):
# Windows 专属的测试代码
pass

def test_maybe_skipped(self):
if not external_resource_available():
self.skipTest("external resource not available")
# 依赖于外部资源的测试代码
pass
1
2
3
4
5
6
7
8
9
test_format (__main__.MyTestCase.test_format) ... skipped 'not supported in this library version'
test_nothing (__main__.MyTestCase.test_nothing) ... skipped 'demonstrating skipping'
test_maybe_skipped (__main__.MyTestCase.test_maybe_skipped) ... skipped 'external resource not available'
test_windows_support (__main__.MyTestCase.test_windows_support) ... skipped 'requires Windows'

----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK (skipped=4)

跳过整个测试类:

1
2
3
4
@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
def test_not_run(self):
pass

表面测试预计会失败:

1
2
3
4
class ExpectedFailureTestCase(unittest.TestCase):
@unittest.expectedFailure
def test_fail(self):
self.assertEqual(1, 0, "broken")

循环迭代测试

当几个测试之间的差异非常小,例如只有某些形参不同时,unittest 允许使用 subTest() 上下文管理器在一个测试方法体的内部区分它们。

1
2
3
4
5
6
7
8
9
class NumbersTest(unittest.TestCase):

def test_even(self):
"""
Test that numbers between 0 and 5 are all even.
"""
for i in range(0, 6):
with self.subTest(i=i):
self.assertEqual(i % 2, 0)

pytest

第三方库,语法简洁,插件丰富,要求 python 3.8+

安装

1
2
pip install --upgrade pytest
pytest --version
上一篇:
Mainline
下一篇:
Readme