Python单元测试实现指南
基本单元测试结构
# 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("除数不能为0")
return a / b
使用unittest框架
# test_calculator.py
import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
"""每个测试方法执行前都会调用"""
self.calc = Calculator()
def test_add(self):
"""测试加法"""
result = self.calc.add(3, 5)
self.assertEqual(result, 8)
self.assertEqual(self.calc.add(-1, 1), 0)
self.assertEqual(self.calc.add(0, 0), 0)
def test_subtract(self):
"""测试减法"""
self.assertEqual(self.calc.subtract(10, 5), 5)
self.assertEqual(self.calc.subtract(5, 10), -5)
def test_multiply(self):
"""测试乘法"""
self.assertEqual(self.calc.multiply(3, 4), 12)
self.assertEqual(self.calc.multiply(-2, 3), -6)
self.assertEqual(self.calc.multiply(0, 5), 0)
def test_divide(self):
"""测试除法"""
self.assertEqual(self.calc.divide(10, 2), 5)
self.assertEqual(self.calc.divide(7, 2), 3.5)
with self.assertRaises(ValueError):
self.calc.divide(10, 0)
def test_divide_by_zero(self):
"""测试除以零异常"""
with self.assertRaises(ValueError) as context:
self.calc.divide(10, 0)
self.assertEqual(str(context.exception), "除数不能为0")
if __name__ == '__main__':
unittest.main()
使用pytest框架(推荐)
# test_calculator_pytest.py
import pytest
from calculator import Calculator
@pytest.fixture
def calculator():
"""fixture提供测试环境"""
return Calculator()
def test_add(calculator):
assert calculator.add(3, 5) == 8
assert calculator.add(-1, 1) == 0
assert calculator.add(0, 0) == 0
def test_subtract(calculator):
assert calculator.subtract(10, 5) == 5
assert calculator.subtract(5, 10) == -5
def test_multiply(calculator):
assert calculator.multiply(3, 4) == 12
assert calculator.multiply(-2, 3) == -6
assert calculator.multiply(0, 5) == 0
def test_divide(calculator):
assert calculator.divide(10, 2) == 5
assert calculator.divide(7, 2) == 3.5
def test_divide_by_zero(calculator):
with pytest.raises(ValueError, match="除数不能为0"):
calculator.divide(10, 0)
测试复杂场景
# user_service.py
class UserService:
def __init__(self, database):
self.database = database
def create_user(self, username, email):
if not username or not email:
raise ValueError("用户名和邮箱不能为空")
if self.database.exists(username):
raise ValueError("用户已存在")
user = {
'username': username,
'email': email,
'active': True
}
return self.database.save(user)
def get_user(self, username):
user = self.database.find(username)
if not user:
return None
return user
def deactivate_user(self, username):
user = self.get_user(username)
if not user:
raise ValueError("用户不存在")
user['active'] = False
return self.database.update(user)
# test_user_service.py
import unittest
from unittest.mock import Mock, patch
from user_service import UserService
class TestUserService(unittest.TestCase):
def setUp(self):
self.mock_database = Mock()
self.service = UserService(self.mock_database)
def test_create_user_success(self):
"""测试成功创建用户"""
self.mock_database.exists.return_value = False
self.mock_database.save.return_value = {
'username': 'testuser',
'email': 'test@example.com',
'active': True
}
result = self.service.create_user('testuser', 'test@example.com')
self.assertEqual(result['username'], 'testuser')
self.assertTrue(result['active'])
self.mock_database.exists.assert_called_once_with('testuser')
self.mock_database.save.assert_called_once()
def test_create_user_exists(self):
"""测试创建已存在的用户"""
self.mock_database.exists.return_value = True
with self.assertRaises(ValueError) as context:
self.service.create_user('existing', 'test@example.com')
self.assertEqual(str(context.exception), "用户已存在")
def test_create_user_empty_input(self):
"""测试空输入"""
with self.assertRaises(ValueError):
self.service.create_user('', 'test@example.com')
with self.assertRaises(ValueError):
self.service.create_user('testuser', '')
def test_get_user_found(self):
"""测试获取存在的用户"""
expected_user = {'username': 'testuser', 'email': 'test@example.com'}
self.mock_database.find.return_value = expected_user
result = self.service.get_user('testuser')
self.assertEqual(result, expected_user)
self.mock_database.find.assert_called_once_with('testuser')
def test_get_user_not_found(self):
"""测试获取不存在的用户"""
self.mock_database.find.return_value = None
result = self.service.get_user('nonexistent')
self.assertIsNone(result)
def test_deactivate_user(self):
"""测试停用用户"""
user = {'username': 'testuser', 'active': True}
self.mock_database.find.return_value = user
self.mock_database.update.return_value = {**user, 'active': False}
result = self.service.deactivate_user('testuser')
self.assertFalse(result['active'])
self.mock_database.update.assert_called_once()
if __name__ == '__main__':
unittest.main()
运行测试
# 运行所有测试 python -m unittest discover # 运行特定测试文件 python -m unittest test_calculator.py # 运行特定测试类 python -m unittest test_calculator.TestCalculator # 运行特定测试方法 python -m unittest test_calculator.TestCalculator.test_add # 使用 pytest 运行 pytest test_calculator_pytest.py -v # 生成覆盖率报告 pip install coverage coverage run -m pytest coverage report -m
实用的断言方法
import unittest
class TestAssertions(unittest.TestCase):
def test_common_assertions(self):
# 相等/不等
self.assertEqual(1 + 1, 2)
self.assertNotEqual(1 + 1, 3)
# 布尔值
self.assertTrue(True)
self.assertFalse(False)
# 空值
self.assertIsNone(None)
self.assertIsNotNone("hello")
# 类型
self.assertIsInstance(42, int)
self.assertNotIsInstance("42", int)
# 列表/元组
self.assertIn("a", ["a", "b", "c"])
self.assertNotIn("x", ["a", "b", "c"])
# 异常
with self.assertRaises(ValueError):
int("abc")
# 近似相等(浮点数)
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)
测试最佳实践
# test_best_practices.py
"""
最佳实践示例:
1. 测试方法命名规范:test_{function_name}_{scenario}
2. 每个测试方法只测试一个功能点
3. 使用 setUp 和 tearDown 管理测试资源
4. 测试边界条件
5. 使用 mock 隔离外部依赖
"""
import unittest
from unittest.mock import patch, MagicMock
import requests
class DataProcessor:
def fetch_and_process(self, url):
response = requests.get(url)
if response.status_code == 200:
data = response.json()
return self._process(data)
return None
def _process(self, data):
return [item['name'] for item in data if item.get('active')]
class TestDataProcessor(unittest.TestCase):
@patch('requests.get')
def test_fetch_and_process_success(self, mock_get):
"""测试成功获取并处理数据"""
# 准备 mock 数据
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = [
{'name': 'item1', 'active': True},
{'name': 'item2', 'active': False},
{'name': 'item3', 'active': True}
]
mock_get.return_value = mock_response
# 执行测试
processor = DataProcessor()
result = processor.fetch_and_process('http://example.com/api')
# 验证结果
self.assertEqual(result, ['item1', 'item3'])
mock_get.assert_called_once_with('http://example.com/api')
@patch('requests.get')
def test_fetch_and_process_failure(self, mock_get):
"""测试请求失败"""
mock_response = MagicMock()
mock_response.status_code = 404
mock_get.return_value = mock_response
processor = DataProcessor()
result = processor.fetch_and_process('http://example.com/api')
self.assertIsNone(result)
if __name__ == '__main__':
unittest.main()
- 选择测试框架:
unittest(内置)或pytest(更简洁) - 测试结构:遵循 AAA 模式(Arrange-Act-Assert)
- 命名规范:
test_功能_场景 - 隔离测试:使用 mock 对象模拟外部依赖
- 覆盖率:关注代码覆盖率和边界情况
- 持续集成:将测试集成到 CI/CD 流程中

版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播