
Python单元测试pytest最佳实践引言单元测试是保证代码质量的关键环节它帮助我们在开发过程中快速发现问题。作为一名从Python转向Rust的后端开发者我在实践中总结了单元测试的最佳实践。本文将深入探讨pytest框架的使用帮助你构建高质量的测试套件。一、单元测试基础概念1.1 什么是单元测试单元测试是对软件中最小可测试单元进行验证的测试方法。1.2 单元测试的重要性快速反馈在开发过程中立即发现问题文档作用测试用例是代码的活文档重构支持安全地进行代码重构质量保障防止回归问题1.3 测试金字塔单元测试底层- 数量最多 | 集成测试中层- 数量适中 | 端到端测试顶层- 数量最少二、pytest入门2.1 安装pytestpip install pytest pytest-cov pytest-mock2.2 基本测试结构# test_example.py def test_addition(): assert 1 1 2 def test_string_length(): assert len(hello) 5 def test_list_contains(): assert 3 in [1, 2, 3, 4, 5]2.3 运行测试pytest test_example.py -v pytest tests/ -v --tbshort pytest -x # 遇到第一个失败就停止 pytest --tbno # 不显示traceback三、测试用例组织3.1 测试类class TestCalculator: def test_add(self): assert 2 3 5 def test_subtract(self): assert 5 - 3 2 def test_multiply(self): assert 4 * 5 20 def test_divide(self): assert 10 / 2 53.2 参数化测试import pytest pytest.mark.parametrize(a, b, expected, [ (1, 2, 3), (10, 20, 30), (-1, 1, 0), (0, 0, 0), ]) def test_add(a, b, expected): assert a b expected pytest.mark.parametrize(input_value, expected, [ (hello, 5), (, 0), (python, 6), ]) def test_string_length(input_value, expected): assert len(input_value) expected3.3 测试文件结构project/ ├── src/ │ └── my_module/ │ ├── __init__.py │ └── calculator.py └── tests/ ├── __init__.py ├── conftest.py └── test_calculator.py四、fixture机制4.1 基本fixture# conftest.py import pytest pytest.fixture def database_connection(): conn create_connection() yield conn conn.close() pytest.fixture def test_user(): return {id: 1, name: Test User}4.2 使用fixturedef test_database_query(database_connection, test_user): result database_connection.query(SELECT * FROM users WHERE id %s, test_user[id]) assert result[name] test_user[name]4.3 fixture作用域pytest.fixture(scopemodule) def module_fixture(): print(Module setup) yield print(Module teardown) pytest.fixture(scopesession) def session_fixture(): print(Session setup) yield print(Session teardown)4.4 参数化fixturepytest.fixture(params[sqlite, postgresql, mysql]) def database_type(request): return request.param def test_database_connection(database_type): assert database_type in [sqlite, postgresql, mysql]五、mock与monkey patch5.1 使用unittest.mockfrom unittest.mock import Mock, patch def test_api_call(): mock_response Mock() mock_response.json.return_value {status: success} with patch(requests.get, return_valuemock_response): result make_api_call(http://example.com) assert result {status: success} requests.get.assert_called_once_with(http://example.com)5.2 使用pytest-mockdef test_with_mock(mocker): mock_function mocker.patch(my_module.my_function) mock_function.return_value mocked result result my_module.use_my_function() assert result mocked result mock_function.assert_called_once()5.3 模拟类方法def test_class_method_mock(mocker): mock_method mocker.patch.object(MyClass, method) mock_method.return_value 42 obj MyClass() result obj.method() assert result 42 mock_method.assert_called_once()六、测试断言6.1 基本断言assert value expected assert value ! expected assert value is None assert value is not None assert value in container assert value not in container assert condition6.2 异常断言def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): 1 / 0 def test_exception_message(): with pytest.raises(ValueError, matchinvalid value): raise ValueError(This is an invalid value)6.3 警告断言def test_deprecation_warning(): with pytest.warns(DeprecationWarning): deprecated_function()七、测试配置7.1 pytest.ini配置[pytest] testpaths [tests] python_files test_*.py python_classes Test* python_functions test_* addopts -v --tbshort7.2 setup.cfg配置[tool:pytest] testpaths [tests] addopts --covsrc --cov-reportterm-missing7.3 tox配置[tox] envlist py38, py39, py310 skipsdist true [testenv] deps pytest pytest-cov pytest-mock commands pytest tests/ --covsrc八、测试覆盖率8.1 生成覆盖率报告pytest --covsrc --cov-reporthtml --cov-reportterm8.2 配置覆盖率阈值[pytest] addopts --covsrc --cov-reportterm --cov-fail-under808.3 排除文件# .coveragerc [run] omit src/__init__.py src/config.py九、测试最佳实践9.1 测试命名规范def test_function_name_should_do_something(): pass def test_widget_when_scenario_then_result(): pass9.2 AAA模式def test_calculator_add(): # Arrange calculator Calculator() # Act result calculator.add(2, 3) # Assert assert result 59.3 隔离性原则def test_database_operations(): # 每个测试用例应该独立 setup_test_database() # 执行测试 result perform_operation() assert result is not None # 清理 teardown_test_database()9.4 避免测试逻辑# 不好的做法 def test_complex_logic(): if condition: assert something else: assert something_else # 好的做法 def test_case_one(): assert something def test_case_two(): assert something_else十、实战案例完整测试套件import pytest from unittest.mock import patch, MagicMock from my_app import UserService, Database, Cache pytest.fixture def mock_database(): return MagicMock(specDatabase) pytest.fixture def mock_cache(): return MagicMock(specCache) pytest.fixture def user_service(mock_database, mock_cache): return UserService(databasemock_database, cachemock_cache) pytest.mark.parametrize(user_id, expected_name, [ (1, Alice), (2, Bob), (3, Charlie), ]) def test_get_user(user_service, mock_database, user_id, expected_name): mock_database.query.return_value {id: user_id, name: expected_name} user user_service.get_user(user_id) assert user[id] user_id assert user[name] expected_name mock_database.query.assert_called_once_with(SELECT * FROM users WHERE id %s, user_id) def test_get_user_cached(user_service, mock_database, mock_cache): mock_cache.get.return_value {id: 1, name: Cached Alice} user user_service.get_user(1) assert user[name] Cached Alice mock_cache.get.assert_called_once_with(user:1) mock_database.query.assert_not_called() def test_create_user_validation(user_service, mock_database): with pytest.raises(ValueError, matchName is required): user_service.create_user(name) def test_create_user_success(user_service, mock_database): mock_database.insert.return_value 42 user_id user_service.create_user(nameNew User) assert user_id 42 mock_database.insert.assert_called_once_with( INSERT INTO users (name) VALUES (%s), New User ) patch(my_app.external_api.send_email) def test_notify_user(mock_send_email, user_service): user {id: 1, email: userexample.com} user_service.notify_user(user) mock_send_email.assert_called_once_with( touserexample.com, subjectWelcome, bodyHello, welcome to our service! )总结单元测试是保证代码质量的关键环节。通过本文的学习你应该掌握了以下核心要点单元测试基础测试金字塔、重要性pytest入门安装、基本结构、运行测试测试组织测试类、参数化测试fixture机制基本fixture、作用域、参数化mock技术unittest.mock、pytest-mock断言技巧基本断言、异常断言、警告断言测试配置pytest.ini、setup.cfg、tox测试覆盖率生成报告、阈值配置最佳实践命名规范、AAA模式、隔离性原则实战案例完整的测试套件作为从Python转向Rust的后端开发者掌握单元测试对于构建高质量系统至关重要。后续文章将深入探讨Rust中的测试框架与模式。