🧪 Python单元测试unittest入门与最佳实践:从assert到Mock

在软件开发中,测试是确保代码质量的关键环节。Python标准库中的unittest模块为我们提供了强大而灵活的单元测试框架,无需额外安装即可使用。本文将带你从入门到进阶,掌握unittest的核心概念与最佳实践。

一、unittest基础概念

unittest是Python内置的单元测试框架,灵感来源于JUnit。核心概念包括:

  • Test Case(测试用例):最小的测试单元,继承自unittest.TestCase
  • Test Suite(测试套件):多个测试用例的集合
  • Test Runner(测试运行器):执行测试并输出结果
  • Fixture(测试夹具):测试前的准备和测试后的清理工作

二、编写第一个测试

创建一个简单的计算器类及其测试:

# calculator.py
class Calculator:
    def add(self, a, b):
        return a + b
    
    def subtract(self, a, b):
        return a - b
    
    def multiply(self, a, b):
        return a * b
    
    def divide(self, a, b):
        if b == 0:
            raise ValueError("除数不能为零")
        return a / b

对应的测试代码:

# test_calculator.py
import unittest
from calculator import Calculator

class TestCalculator(unittest.TestCase):
    def setUp(self):
        # 每个测试方法前执行
        self.calc = Calculator()
    
    def tearDown(self):
        # 每个测试方法后执行
        pass
    
    def test_add(self):
        self.assertEqual(self.calc.add(2, 3), 5)
        self.assertEqual(self.calc.add(-1, 1), 0)
    
    def test_subtract(self):
        self.assertEqual(self.calc.subtract(10, 5), 5)
        self.assertEqual(self.calc.subtract(-1, -1), 0)
    
    def test_multiply(self):
        self.assertEqual(self.calc.multiply(3, 4), 12)
        self.assertEqual(self.calc.multiply(-2, 3), -6)
    
    def test_divide(self):
        self.assertEqual(self.calc.divide(10, 2), 5)
        self.assertAlmostEqual(self.calc.divide(7, 2), 3.5)
    
    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            self.calc.divide(10, 0)

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

三、常用断言方法

unittest.TestCase提供了丰富的断言方法:

方法用途
assertEqual(a, b) 验证a == b
assertNotEqual(a, b) 验证a != b
assertTrue(x) 验证x为True
assertFalse(x) 验证x为False
assertIs(a, b) 验证a is b
assertIsNone(x) 验证x is None
assertIn(a, b) 验证a in b
assertIsInstance(a, b) 验证isinstance(a, b)
assertRaises(exc) 验证抛出异常
assertAlmostEqual(a, b) 验证浮点数近似相等

四、高级特性

1. 跳过测试与预期失败

import unittest
import sys

class TestAdvanced(unittest.TestCase):
    @unittest.skip("暂时跳过此测试")
    def test_skip(self):
        pass
    
    @unittest.skipIf(sys.platform == 'win32', 'Windows平台跳过')
    def test_skip_windows(self):
        pass
    
    @unittest.expectedFailure
    def test_expected_failure(self):
        self.assertEqual(1, 2)  # 已知会失败

2. 使用Mock对象

from unittest.mock import Mock, patch, MagicMock

# 创建Mock对象
mock = Mock()
mock.return_value = 42
print(mock())  # 输出: 42

# 使用patch装饰器
import requests

class TestAPI(unittest.TestCase):
    @patch('requests.get')
    def test_fetch_data(self, mock_get):
        mock_get.return_value.status_code = 200
        mock_get.return_value.json.return_value = {'data': 'test'}
        
        result = requests.get('https://api.example.com')
        self.assertEqual(result.status_code, 200)
        mock_get.assert_called_once()

五、测试组织与发现

对于大型项目,合理的测试组织至关重要:

project/
├── src/
│   ├── __init__.py
│   └── calculator.py
└── tests/
    ├── __init__.py
    ├── test_calculator.py
    └── test_integration.py

运行测试的多种方式:

# 运行单个测试文件
python -m unittest test_calculator

# 运行具体测试类
python -m unittest test_calculator.TestCalculator

# 运行具体测试方法
python -m unittest test_calculator.TestCalculator.test_add

# 自动发现并运行所有测试
python -m unittest discover -s tests -v

# 生成HTML测试报告(需安装html-testRunner)
pip install html-testRunner

六、最佳实践总结

  1. 测试命名:使用test_前缀,描述清楚测试意图
  2. 独立性:每个测试应独立运行,不依赖其他测试
  3. 单一职责:一个测试只验证一个概念
  4. 使用setUp/tearDown:合理管理测试资源
  5. Mock外部依赖:单元测试应隔离外部系统
  6. 覆盖率目标:核心代码建议达到80%以上覆盖率

使用coverage工具检查测试覆盖率:

pip install coverage
coverage run -m unittest discover
coverage report
coverage html  # 生成HTML报告

总结

unittest作为Python标准库的测试框架,功能完备且无需额外依赖。掌握其基础用法后,配合Mock和覆盖率工具,可以构建稳健的测试体系。对于更现代的测试需求,也可以考虑pytest,它与unittest完全兼容且语法更简洁。

参考资料

(本文内容由AI生成,仅供学习参考)

posted @ 2026-04-16 10:24  码小小小仙  阅读(9)  评论(0)    收藏  举报