| 30天学会Python编程:29.Python单元测试
					当前位置:点晴教程→知识管理交流
					
					→『 技术文档交流 』
					
				 
 
 | 
| test_addition() | ||
| setUp()tearDown() | ||
| assertEqual(result, 5) | ||
| suite.addTest(TestMath) | ||
| unittest.TextTestRunner() | 
import unittest
classMathOperations:
    defadd(self, a, b):
        return a + b
classTestMathOperations(unittest.TestCase):
    defsetUp(self):
        """每个测试方法前执行"""
        self.math = MathOperations()
    
    deftest_add_integers(self):
        """测试整数加法"""
        result = self.math.add(5, 3)
        self.assertEqual(result, 8)
        
    deftest_add_floats(self):
        """测试浮点数加法"""
        result = self.math.add(2.5, 3.1)
        self.assertAlmostEqual(result, 5.6, places=1)
    
    deftearDown(self):
        """每个测试方法后执行"""
        delself.math
if __name__ == '__main__':
    unittest.main()
| assertEqual(a, b) | self.assertEqual(sum([1,2,3]), 6) | |
| assertNotEqual(a, b) | self.assertNotEqual('a', 'b') | |
| assertTrue(x) | self.assertTrue(10 > 5) | |
| assertFalse(x) | self.assertFalse(5 > 10) | |
| assertIs(a, b) | self.assertIs(obj1, obj2) | |
| assertIsNone(x) | self.assertIsNone(result) | |
| assertIn(a, b) | self.assertIn(3, [1,2,3]) | |
| assertRaises(exc) | with self.assertRaises(ValueError): | |
| assertAlmostEqual(a, b) | self.assertAlmostEqual(0.1+0.2, 0.3, places=7) | 
class DatabaseTest(unittest.TestCase):
    @classmethod
    defsetUpClass(cls):
        """整个测试类执行前调用一次"""
        cls.db = create_test_database()
        cls.db.connect()
    
    defsetUp(self):
        """每个测试方法前调用"""
        self.cursor = self.db.create_cursor()
        self.cursor.execute("BEGIN TRANSACTION")
    
    deftest_insert(self):
        self.cursor.execute("INSERT INTO users VALUES ('Alice')")
        # 验证插入操作...
    
    deftearDown(self):
        """每个测试方法后调用"""
        self.cursor.execute("ROLLBACK")
        self.cursor.close()
    
    @classmethod
    deftearDownClass(cls):
        """整个测试类执行后调用一次"""
        cls.db.disconnect()
        cls.db.drop()
# content of test_math.py
defadd(a, b):
    return a + b
deftest_add_integers():
    assert add(5, 3) == 8
deftest_add_floats():
    """测试浮点数加法"""
    result = add(2.5, 3.1)
    assertabs(result - 5.6) < 0.001
deftest_add_strings():
    """测试字符串拼接"""
    assert add("Hello", "World") == "HelloWorld"
import pytest
@pytest.fixture(scope="module")
defdatabase():
    """模块级数据库固件"""
    db = create_test_db()
    yield db  # 测试执行后继续执行清理
    db.drop()
@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-5, 5, 0),
    (100, 200, 300),
])
deftest_addition(a, b, expected):
    assert add(a, b) == expected
@pytest.mark.slow
deftest_large_number_addition():
    """标记为慢测试"""
    assert add(10**18, 1) == 10**18 + 1
@pytest.mark.skipif(sys.version_info < (3, 8), 
                   reason="需要Python 3.8或更高版本")
deftest_walrus_operator():
    """跳过特定Python版本的测试"""
    assert (result := add(3, 4)) == 7
| Mock | ||
| Stub | ||
| Spy | ||
| Fake | 
from unittest.mock import MagicMock, patch, PropertyMock
classPaymentProcessor:
    defprocess_payment(self, amount, card_number):
        # 实际实现调用外部支付网关
        pass
classTestOrder(unittest.TestCase):
    @patch('payment.PaymentProcessor.process_payment')
    deftest_order_payment(self, mock_process):
        """测试订单支付流程"""
        order = Order(total=100)
        order.pay("4111111111111111")
        
        # 验证支付方法被调用
        mock_process.assert_called_once_with(100, "4111111111111111")
    
    deftest_inventory_update(self):
        """测试库存更新"""
        with patch('inventory.Inventory.decrease') as mock_decrease:
            order = Order(items=[{"id": 1, "quantity": 2}])
            order.process()
            
            # 验证库存更新方法被调用
            mock_decrease.assert_called_once_with(1, 2)
    
    @patch.object(Product, 'price', new_callable=PropertyMock)
    deftest_discount_calculation(self, mock_price):
        """测试折扣计算逻辑"""
        mock_price.return_value = 100
        product = Product(id=1)
        
        # 测试折扣逻辑
        discount = calculate_discount(product, 20)
        self.assertEqual(discount, 20)
import unittest
from parameterized import parameterized
class TestMath(unittest.TestCase):
    @parameterized.expand([
        ("positive", 5, 3, 8),
        ("zero", 0, 0, 0),
        ("negative", -5, 5, 0),
        ("large_numbers", 10**6, 10**6, 2*10**6),
    ])
    def test_add(self, name, a, b, expected):
        result = add(a, b)
        self.assertEqual(result, expected)
import pytest
import csv
defload_test_data():
    """从CSV文件加载测试数据"""
    withopen('test_data.csv', 'r') as f:
        reader = csv.DictReader(f)
        return [tuple(row.values()) for row in reader]
@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (0, 0, 0),
    pytest.param(-5, 5, 0, id="negative_and_positive"),
    pytest.param(100, 200, 300, marks=pytest.mark.slow),
])
deftest_addition(a, b, expected):
    assert add(a, b) == expected
@pytest.mark.parametrize("a,b,expected", load_test_data())
deftest_from_csv(a, b, expected):
    """从外部数据源加载测试用例"""
    a = int(a)
    b = int(b)
    expected = int(expected)
    assert add(a, b) == expected
| 语句覆盖率 | ||
| 分支覆盖率 | ||
| 函数覆盖率 | ||
| 行覆盖率 | ||
| 条件覆盖率 | 
# 安装插件
pip install pytest-cov
# 运行测试并生成报告
pytest --cov=my_project --cov-report=html
# 检查最小覆盖率
pytest --cov=my_project --cov-fail-under=90
# .coveragerc 配置文件
[run]
source = my_project
branch = True# 启用分支覆盖
omit = 
    */__init__.py
    */tests/*
[report]
show_missing = True# 显示未覆盖行
exclude_lines = 
    pragma: no cover
    def __repr__
    if __name__ == .__main__.:

# 步骤1:编写失败测试
deftest_fibonacci():
    assert fibonacci(0) == 0
    assert fibonacci(1) == 1
    assert fibonacci(2) == 1
    assert fibonacci(5) == 5
# 步骤2:实现最小功能
deffibonacci(n):
    if n == 0: 
        return0
    elif n == 1:
        return1
    else:
        return fibonacci(n-1) + fibonacci(n-2)
# 步骤3:重构改进(添加缓存优化)
from functools import lru_cache
@lru_cache(maxsize=None)
deffibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

pytest-xdist并行执行
pytest -n auto  # 自动检测CPU核心数
@pytest.mark.slow
def test_large_data_processing():
    # 慢测试...
def user_factory(**kwargs):
    defaults = {"name": "Test", "email": "test@example.com"}
    return {**defaults, **kwargs}
| 上帝测试 | ||
| 测试耦合 | ||
| 实现细节测试 | ||
| 不稳定测试 | ||
| 慢测试 | 
from fastapi.testclient import TestClient
from myapp.main import app
client = TestClient(app)
deftest_create_user():
    response = client.post(
        "/users/",
        json={"name": "Alice", "email": "alice@example.com"}
    )
    assert response.status_code == 201
    assert response.json()["name"] == "Alice"
    
    # 验证用户是否创建成功
    user_id = response.json()["id"]
    get_response = client.get(f"/users/{user_id}")
    assert get_response.status_code == 200
    assert get_response.json()["email"] == "alice@example.com"
@patch("myapp.services.send_welcome_email")
deftest_user_creation_sends_email(mock_send):
    client.post("/users/", json={"name": "Bob", "email": "bob@example.com"})
    mock_send.assert_called_once_with("bob@example.com")
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture(scope="module")
deftest_db():
    # 内存数据库用于测试
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    yield Session()
    # 测试后自动清理
    Base.metadata.drop_all(engine)
deftest_create_user(test_db):
    user = User(name="Alice", email="alice@example.com")
    test_db.add(user)
    test_db.commit()
    
    saved_user = test_db.query(User).filter_by(email="alice@example.com").first()
    assert saved_user isnotNone
    assert saved_user.name == "Alice"
    
    # 测试唯一约束
    duplicate = User(name="Alice2", email="alice@example.com")
    test_db.add(duplicate)
    with pytest.raises(IntegrityError):
        test_db.commit()

test_<功能>_<条件>_<预期结果>def test_add_positive_numbers_returns_sum()
def test_login_with_invalid_credentials_raises_error()
Test<模块名>class TestUserModel(unittest.TestCase):
project/
├── src/
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
└── tests/
    ├── unit/
    │   ├── test_module1.py
    │   └── test_module2.py
    ├── integration/
    │   └── test_integration.py
    └── e2e/
        └── test_e2e.py
单元测试是Python开发中的核心实践,提供以下关键价值:
高级进阶方向:
tox进行多环境测试"没有测试的代码就是坏代码,不管它写得多么优雅" - Martin Fowler
通过持续实践单元测试,我们能构建更健壮、可维护的Python应用程序,显著提高开发效率和代码质量。
阅读原文:原文链接