unittest相关
Python unittest 单元测试框架 系统学习大纲
一、 unittest 框架概述
- 1.1 单元测试的重要性
- 验证代码最小单元(函数、方法、类)的正确性
- 提高代码质量,减少bug
- 支持重构,确保修改不会破坏现有功能
- 作为代码文档,展示如何使用被测试代码
- 1.2 unittest 框架简介
- Python标准库自带的单元测试框架
- 受到JUnit的启发,采用xUnit风格
- 提供完整的测试基础设施:测试发现、执行、报告
- 1.3 unittest 核心概念
- 测试用例 (TestCase):测试的基本单元
- 测试固件 (Fixture):测试运行前后的设置和清理
- 测试套件 (TestSuite):测试用例的集合
- 测试运行器 (TestRunner):执行测试并展示结果
- 断言 (Assertion):验证代码行为是否符合预期
二、 环境搭建与第一个测试
- 2.1 unittest 内置特性
- Python标准库的一部分,无需额外安装
- 适用于Python 2.7+ 和 Python 3.x
- 2.2 创建第一个测试用例
python 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()) if __name__ == '__main__': unittest.main()
三、 测试用例 (TestCase) 详解
- 3.1 测试方法命名规范
- 测试方法必须以
test_开头 - 这是测试发现机制识别测试方法的方式
- 方法名应该描述要测试的功能
- 测试方法必须以
- 3.2 测试类的组织结构
- 继承
unittest.TestCase类 - 一个测试类通常测试一个模块或一个类
- 相关的测试方法组织在同一个测试类中
- 继承
- 3.3 跳过测试
- 使用
@unittest.skip(reason)装饰器无条件跳过 - 使用
@unittest.skipIf(condition, reason)条件跳过 - 使用
@unittest.skipUnless(condition, reason)条件跳过 - 在测试方法中使用
self.skipTest(reason)方法跳过
- 使用
- 3.4 预期失败测试
- 使用
@unittest.expectedFailure装饰器标记预期失败的测试 - 如果测试通过,则标记为”unexpected success”
- 如果测试失败,则标记为”expected failure”
- 使用
四、 断言方法 (Assertions)
- 4.1 基本断言方法
assertEqual(a, b, msg=None):a == bassertNotEqual(a, b, msg=None):a != bassertTrue(x, msg=None):bool(x) is TrueassertFalse(x, msg=None):bool(x) is FalseassertIs(a, b, msg=None):a is bassertIsNot(a, b, msg=None):a is not bassertIsNone(x, msg=None):x is NoneassertIsNotNone(x, msg=None):x is not NoneassertIn(a, b, msg=None):a in bassertNotIn(a, b, msg=None):a not in b
- 4.2 比较断言方法
assertGreater(a, b, msg=None):a > bassertGreaterEqual(a, b, msg=None):a >= bassertLess(a, b, msg=None):a < bassertLessEqual(a, b, msg=None):a <= b
- 4.3 异常和警告断言
assertRaises(exception, callable, *args, **kwargs):验证调用callable时抛出指定异常assertRaisesRegex(exception, regex, callable, *args, **kwargs):验证异常消息匹配正则表达式assertWarns(warning, callable, *args, **kwargs):验证调用callable时发出指定警告assertWarnsRegex(warning, regex, callable, *args, **kwargs):验证警告消息匹配正则表达式
- 4.4 序列和容器断言
assertListEqual(a, b, msg=None):比较列表assertTupleEqual(a, b, msg=None):比较元组assertSetEqual(a, b, msg=None):比较集合assertDictEqual(a, b, msg=None):比较字典assertSequenceEqual(a, b, msg=None):比较序列assertMultiLineEqual(a, b, msg=None):比较多行字符串
- 4.5 近似值断言
assertAlmostEqual(a, b, places=7, msg=None):round(a-b, places) == 0assertNotAlmostEqual(a, b, places=7, msg=None):round(a-b, places) != 0
- 4.6 正则表达式断言
assertRegex(text, regex, msg=None):text匹配regexassertNotRegex(text, regex, msg=None):text不匹配regex
- 4.7 自定义失败消息
- 所有断言方法都接受可选的
msg参数 - 当断言失败时,会显示自定义消息
- 所有断言方法都接受可选的
五、 测试固件 (Test Fixtures)
- 5.1 方法级别的固件
setUp():在每个测试方法执行前调用tearDown():在每个测试方法执行后调用- 用于准备测试环境和清理资源
- 5.2 类级别的固件
setUpClass():在整个测试类开始执行前调用一次(使用@classmethod装饰器)tearDownClass():在整个测试类结束后调用一次(使用@classmethod装饰器)- 用于执行耗时的初始化操作,如数据库连接
- 5.3 模块级别的固件
setUpModule():在模块中的所有测试开始前执行tearDownModule():在模块中的所有测试结束后执行- 直接在模块中定义,不是测试类的一部分
- 5.4 固件执行顺序
setUpModule()setUpClass()setUp()- 测试方法
tearDown()tearDownClass()tearDownModule()
六、 测试套件 (TestSuite) 与测试发现
- 6.1 手动创建测试套件
import unittest def suite(): suite = unittest.TestSuite() suite.addTest(TestStringMethods('test_upper')) suite.addTest(TestStringMethods('test_isupper')) # 或添加整个测试类 suite.addTest(unittest.makeSuite(TestStringMethods)) return suite if __name__ == '__main__': runner = unittest.TextTestRunner() runner.run(suite()) - 6.2 自动测试发现
unittest.discover(start_dir, pattern='test*.py', top_level_dir=None)- 自动发现指定目录下的所有测试
- 命令行使用:
python -m unittest discover -s project_directory -p "test_*.py"
- 6.3 测试发现的参数
-s或--start-directory:开始发现的目录-p或--pattern:匹配测试文件的模式-t或--top-level-directory:项目的顶级目录-v或--verbose:详细输出
七、 测试运行器 (TestRunner) 与测试结果
- 7.1 TextTestRunner
- 默认的测试运行器
- 在控制台输出文本格式的结果
- 可以指定详细程度:
runner = unittest.TextTestRunner(verbosity=2)
- 7.2 测试结果对象
TestResult类存储测试结果- 包含测试通过、失败、错误、跳过的数量
- 可以自定义结果处理
- 7.3 自定义测试运行器
- 继承
unittest.TextTestRunner类 - 重写
_makeResult方法 - 创建自定义的
TestResult子类
- 继承
- 7.4 测试结果格式
.:测试通过F:测试失败E:测试错误s:测试跳过x:预期失败u:意外成功
八、 Mock 对象与补丁
- 8.1 unittest.mock 模块简介
- Python 3.3+ 内置,Python 2.7 需要单独安装
mock库 - 用于替换测试中的依赖项
- 创建模拟对象,验证调用
- Python 3.3+ 内置,Python 2.7 需要单独安装
- 8.2 Mock 对象
- 创建Mock对象:
mock_obj = Mock() - 配置返回值:
mock_obj.method.return_value = 42 - 配置副作用:
mock_obj.method.side_effect = Exception('Error') - 验证调用:
mock_obj.method.assert_called_once_with(arg1, arg2)
- 创建Mock对象:
- 8.3 MagicMock 对象
MagicMock是Mock的子类- 预定义了所有魔术方法
- 适用于模拟需要支持Python协议的对象
- 8.4 补丁 (patching)
- 装饰器方式:
@patch('module.ClassName') - 上下文管理器方式:
with patch('module.function'): - 手动起停方式:
patcher = patch('module.attribute'); mock_obj = patcher.start() - 可以补丁类、函数、属性、字典等
- 装饰器方式:
- 8.5 patch.object
- 补丁对象的特定属性
@patch.object(SomeClass, 'class_method')
- 8.6 常见mock使用场景
- 模拟外部API调用
- 模拟数据库操作
- 模拟文件系统操作
- 模拟网络请求
- 模拟随机数生成
九、 子测试 (Subtests)
- 9.1 子测试的概念
- 允许在一个测试方法中运行多个相似的测试
- 当子测试失败时,不会终止整个测试方法
- 可以识别是哪个子测试失败了
- 9.2 使用子测试
class NumbersTest(unittest.TestCase): def test_even(self): for i in range(0, 6): with self.subTest(i=i): self.assertEqual(i % 2, 0) - 9.3 子测试的优势
- 减少重复代码
- 更清晰的测试输出
- 并行测试多个场景
十、 测试加载器与自定义
- 10.1 TestLoader 类
- 负责加载测试
- 默认使用
unittest.defaultTestLoader - 可以自定义测试加载规则
- 10.2 自定义测试加载
- 重写
loadTestsFromModule、loadTestsFromTestCase、loadTestsFromName方法 - 自定义测试发现逻辑
- 重写
- 10.3 测试用例排序
- 默认按测试方法名的字母顺序执行
- 使用
unittest.TestLoader.sortTestMethodsUsing自定义排序 - 可以实现测试之间的依赖关系
十一、 与其它工具集成
- 11.1 与 doctest 集成
unittest可以运行doctest测试unittest.main()会自动发现和运行doctest
- 11.2 测试覆盖率
- 使用
coverage.py测量测试覆盖率 - 安装:
pip install coverage - 运行:
coverage run -m unittest discover - 报告:
coverage report或coverage html
- 使用
- 11.3 与 pytest 结合使用
pytest可以运行unittest测试用例- 可以混合使用
unittest和pytest风格
- 11.4 持续集成
- Jenkins, Travis CI, GitHub Actions等CI工具支持unittest
- 通常通过
python -m unittest discover运行测试 - 可以生成JUnit格式的XML报告
十二、 最佳实践与常见模式
- 12.1 测试组织结构
- 测试代码与生产代码分离
- 测试文件以
test_开头 - 测试类以
Test开头 - 测试方法以
test_开头
- 12.2 测试设计原则
- 每个测试只测试一个功能
- 测试应该是独立的,不依赖执行顺序
- 使用有意义的测试名称
- 测试应该快速执行
- 12.3 测试数据库操作
- 使用内存数据库(如SQLite)
- 使用测试固件准备测试数据
- 每个测试后清理数据
- 12.4 测试Web应用
- 使用测试客户端模拟HTTP请求
- Flask-Testing, Django测试工具
- 12.5 测试异步代码
- Python 3.8+ 支持异步测试
- 使用
IsolatedAsyncioTestCase - 使用
async/await语法
十三、 常见问题与调试
- 13.1 测试失败与错误
- 失败 (Failure):断言失败
- 错误 (Error):测试代码本身有异常
- 查看详细的失败信息定位问题
- 13.2 测试调试技巧
- 使用
pdb调试器 - 在测试中打印调试信息
- 使用
-v参数获取详细输出
- 使用
- 13.3 测试性能问题
- 避免在测试中执行耗时操作
- 使用mock替代外部依赖
- 考虑使用
setUpClass代替setUp
十四、 实战示例与项目结构
- 14.1 简单函数测试
# calculator.py def add(a, b): return a + b # test_calculator.py import unittest from calculator import add class TestCalculator(unittest.TestCase): def test_add_positive_numbers(self): self.assertEqual(add(2, 3), 5) def test_add_negative_numbers(self): self.assertEqual(add(-1, -1), -2) - 14.2 类测试示例
- 14.3 使用mock的测试示例
- 14.4 典型项目结构
project/ ├── src/ │ ├── __init__.py │ ├── module1.py │ └── module2.py ├── tests/ │ ├── __init__.py │ ├── test_module1.py │ ├── test_module2.py │ └── integration/ │ └── test_integration.py ├── setup.py └── requirements.txt
十五、 进阶主题
- 15.1 参数化测试
- 使用第三方库
parameterized - 安装:
pip install parameterized - 使用
@parameterized.expand装饰器
- 使用第三方库
- 15.2 测试夹具工厂
- 创建可重用的测试数据
- 使用工厂模式生成测试对象
- 15.3 测试并行执行
- 使用第三方库加速测试执行
pytest-xdist支持并行运行unittest测试
- 15.4 自定义断言
- 创建领域特定的断言方法
- 继承
unittest.TestCase并添加自定义断言
十六、 资源与进一步学习
- 16.1 官方文档
- Python官方unittest文档
unittest.mock文档
- 16.2 相关工具
pytest:功能更丰富的测试框架nose2:unittest的扩展coverage.py:测试覆盖率工具
- 16.3 最佳实践资源
- 《Python测试驱动开发》
- 《Python工匠:案例、技巧与工程实践》
- 开源项目的测试代码参考