Skip to content

测试基础 ​

测试确保代码正确性,支持重构和协作。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.py

pytest(推荐) ​

更简洁、强大的测试框架:

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) == 5

Parametrize ​

参数化测试:

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 文档

练习:答案

用心记录每一步成长