测试基础
测试确保代码正确性,支持重构和协作。Python 有 unittest(标准库)和 pytest(推荐)两大测试框架。
为什么需要测试
无测试的问题:
python
def calculate_discount(price, percent):
return price * percent / 100
# 手动验证
print(calculate_discount(100, 20)) # 期望 20,实际?有测试的好处:
- 自动验证正确性
- 支持重构
- 文档化代码行为
- 提高信心
unittest 基础
Python 标准库的测试框架:
python
# test_calculator.py
import unittest
def add(a, b):
return a + b
class TestCalculator(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative(self):
self.assertEqual(add(-1, -1), -2)
def test_add_zero(self):
self.assertEqual(add(5, 0), 5)
if __name__ == '__main__':
unittest.main()运行:
bash
python test_calculator.py
# 或
python -m unittest test_calculator.pypytest(推荐)
更简洁、强大的测试框架:
bash
pip install pytest基本用法:
python
# test_calculator.py
def add(a, b):
return a + b
def test_add_positive():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
def test_add_zero():
assert add(5, 0) == 5运行:
bash
pytest # 运行所有测试
pytest test_calculator.py # 运行单个文件
pytest -v # 详细输出
pytest -k "add" # 运行名称包含 add 的测试输出:
============================= test session starts ==============================
collected 3 items
test_calculator.py ... [100%]
============================== 3 passed in 0.01s ===============================测试组织
Fixtures
共享测试数据:
python
import pytest
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
def test_length(sample_data):
assert len(sample_data) == 5Parametrize
参数化测试:
python
import pytest
@pytest.mark.parametrize("a,b,expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
(10, -5, 5),
])
def test_add(a, b, expected):
assert add(a, b) == expected一个测试生成 4 个用例,简洁高效。
异常测试
python
import pytest
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
def test_divide_normal():
assert divide(10, 2) == 5测试覆盖率
衡量测试覆盖的代码比例:
bash
pip install pytest-cov
# 运行并生成覆盖率报告
pytest --cov=my_package --cov-report=html查看报告:
bash
open htmlcov/index.html目标:80%+ 覆盖率是良好实践。
测试金字塔
/\
/ \ E2E (少量)
/----\
/ 集成 \ (适量)
/--------\
/ 单元测试 \ (大量)
/------------\- 单元测试:测试单个函数/类(70%)
- 集成测试:测试模块交互(20%)
- 端到端测试:测试完整流程(10%)
实战案例:测试 HTTP API
python
# api.py
import requests
def get_user(user_id):
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status()
return response.json()
# test_api.py
import pytest
from unittest.mock import patch
from api import get_user
@patch('api.requests.get')
def test_get_user_success(mock_get):
# 模拟成功响应
mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
mock_get.return_value.status_code = 200
result = get_user(1)
assert result["name"] == "Alice"
mock_get.assert_called_once_with("https://api.example.com/users/1")
@patch('api.requests.get')
def test_get_user_not_found(mock_get):
# 模拟 404 错误
mock_get.return_value.raise_for_status.side_effect = requests.HTTPError("404")
with pytest.raises(requests.HTTPError):
get_user(999)测试类型
单元测试
python
def test_add():
assert add(2, 3) == 5 # 测试单个函数集成测试
python
def test_user_workflow():
user = create_user("Alice")
db.save(user)
loaded = db.load(user.id)
assert loaded.name == "Alice" # 测试多个模块交互端到端测试
python
def test_complete_checkout():
# 完整的用户流程
login("user@example.com")
add_to_cart("product-123")
checkout()
assert order_confirmed()小结
- pytest 比 unittest 更简洁、强大
- fixtures 共享测试数据
- parametrize 简化多用例测试
- 覆盖率 目标 80%+
- 测试金字塔 指导测试比例
- 单元测试为主,集成和 E2E 为辅
下一节学习 Mock 和测试替身。
参考:pytest 文档
练习:答案