unittest相关

3 minute read

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 == b
    • assertNotEqual(a, b, msg=None):a != b
    • assertTrue(x, msg=None):bool(x) is True
    • assertFalse(x, msg=None):bool(x) is False
    • assertIs(a, b, msg=None):a is b
    • assertIsNot(a, b, msg=None):a is not b
    • assertIsNone(x, msg=None):x is None
    • assertIsNotNone(x, msg=None):x is not None
    • assertIn(a, b, msg=None):a in b
    • assertNotIn(a, b, msg=None):a not in b
  • 4.2 比较断言方法
    • assertGreater(a, b, msg=None):a > b
    • assertGreaterEqual(a, b, msg=None):a >= b
    • assertLess(a, b, msg=None):a < b
    • assertLessEqual(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) == 0
    • assertNotAlmostEqual(a, b, places=7, msg=None):round(a-b, places) != 0
  • 4.6 正则表达式断言
    • assertRegex(text, regex, msg=None):text匹配regex
    • assertNotRegex(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 固件执行顺序
    1. setUpModule()
    2. setUpClass()
    3. setUp()
    4. 测试方法
    5. tearDown()
    6. tearDownClass()
    7. 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库
    • 用于替换测试中的依赖项
    • 创建模拟对象,验证调用
  • 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)
  • 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工匠:案例、技巧与工程实践》
    • 开源项目的测试代码参考