1. Python单元测试基础认知单元测试是软件开发中不可或缺的一环它专注于验证代码中最小的可测试单元通常是函数或方法的正确性。Python内置的unittest框架为我们提供了完整的测试解决方案它借鉴了JUnit的设计理念采用面向对象的方式组织测试用例。重要提示良好的单元测试覆盖率可以降低约40%的生产环境缺陷率这是Martin Fowler在《重构》中引用的行业研究数据unittest框架包含四个核心概念测试夹具Test Fixture通过setUp()和tearDown()方法管理测试环境测试用例Test Case继承unittest.TestCase的最小测试单元测试套件Test Suite测试用例的集合测试运行器Test Runner执行并输出测试结果的组件2. unittest框架实战演练2.1 基础测试用例编写我们先创建一个计算器类作为被测对象calculator.pyclass Calculator: 简易计算器实现 def add(self, a, b): 加法运算 if not isinstance(a, (int, float)) or not isinstance(b, (int, float)): raise TypeError(参数必须是数值类型) 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(除数不能为零) return a / b对应的测试用例test_calculator.pyimport unittest from calculator import Calculator class TestCalculator(unittest.TestCase): Calculator类测试用例 def setUp(self): 每个测试方法执行前运行 self.calc Calculator() def test_add_integers(self): 整数加法测试 result self.calc.add(2, 3) self.assertEqual(result, 5) def test_add_floats(self): 浮点数加法测试 result self.calc.add(2.5, 3.7) self.assertAlmostEqual(result, 6.2, places1) def test_add_type_error(self): 类型错误测试 with self.assertRaises(TypeError): self.calc.add(two, 3) def test_divide_by_zero(self): 除零异常测试 with self.assertRaises(ValueError): self.calc.divide(10, 0) def test_multiply_negative(self): 负数乘法测试 result self.calc.multiply(-2, 3) self.assertEqual(result, -6) if __name__ __main__: unittest.main()2.2 高级断言方法unittest提供了丰富的断言方法断言方法检查条件适用场景assertEqual(a, b)a b常规相等性检查assertNotEqual(a, b)a ! b不等性检查assertTrue(x)bool(x) is True布尔真值检查assertFalse(x)bool(x) is False布尔假值检查assertIs(a, b)a is b同一性检查assertIsNot(a, b)a is not b非同一性检查assertIsNone(x)x is NoneNone值检查assertIsNotNone(x)x is not None非None检查assertIn(a, b)a in b包含关系检查assertNotIn(a, b)a not in b不包含检查assertIsInstance(a, b)isinstance(a, b)类型检查assertNotIsInstance(a, b)not isinstance(a, b)非类型检查assertAlmostEqual(a, b)round(a-b, 7) 0浮点数近似相等assertNotAlmostEqual(a, b)round(a-b, 7) ! 0浮点数不近似相等assertRaises(exc, fun, *args, **kwds)fun(*args, **kwds) raises exc异常检查2.3 测试发现与组织unittest支持自动发现测试# 发现并运行当前目录下所有test_*.py文件 python -m unittest discover # 指定测试目录 python -m unittest discover -s tests # 运行单个测试模块 python -m unittest test_module # 运行单个测试类 python -m unittest test_module.TestClass # 运行单个测试方法 python -m unittest test_module.TestClass.test_method3. 测试覆盖率与质量保障3.1 安装覆盖率工具pip install coverage3.2 生成覆盖率报告# 运行测试并收集覆盖率数据 coverage run -m unittest discover # 生成控制台报告 coverage report -m # 生成HTML报告 coverage html理想的覆盖率目标核心业务逻辑100%工具类/辅助函数90%整体项目80%实践建议不要盲目追求100%覆盖率应该优先保证核心业务逻辑的完整覆盖4. 高级测试技巧4.1 参数化测试使用subTest实现参数化测试class TestParameterized(unittest.TestCase): def test_multiple_cases(self): 使用subTest进行参数化测试 test_cases [ (1, 1, 2), (2, 3, 5), (-1, -1, -2), (0, 0, 0) ] for a, b, expected in test_cases: with self.subTest(f{a}{b}{expected}): result a b self.assertEqual(result, expected)4.2 跳过测试与条件跳过class TestSkip(unittest.TestCase): unittest.skip(演示跳过测试) def test_skip(self): self.fail(不应该执行) unittest.skipIf(1 0, 条件为真时跳过) def test_skip_if(self): self.fail(不应该执行) unittest.skipUnless(sys.platform.startswith(win), 需要Windows平台) def test_windows_only(self): # Windows特定测试 pass4.3 模拟对象Mockfrom unittest.mock import Mock, patch class TestMock(unittest.TestCase): def test_mock_method(self): 模拟方法调用 mock Mock() mock.method.return_value mocked self.assertEqual(mock.method(), mocked) mock.method.assert_called_once() patch(os.getcwd) def test_patch_decorator(self, mock_getcwd): 使用patch装饰器模拟 mock_getcwd.return_value /fake/path self.assertEqual(os.getcwd(), /fake/path)5. Django项目中的单元测试5.1 模型测试from django.test import TestCase from myapp.models import Product class ProductModelTest(TestCase): def setUp(self): Product.objects.create( name测试产品, price99.99, stock100 ) def test_product_creation(self): 测试产品创建 product Product.objects.get(name测试产品) self.assertEqual(product.price, 99.99) self.assertEqual(product.stock, 100) def test_price_validation(self): 测试价格验证 from django.core.exceptions import ValidationError product Product(name无效价格, price-10) with self.assertRaises(ValidationError): product.full_clean()5.2 视图测试from django.urls import reverse from django.test import TestCase class ProductViewTest(TestCase): def test_product_list_view(self): 测试产品列表视图 response self.client.get(reverse(product-list)) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, products/list.html) self.assertContains(response, 产品列表) def test_product_create_view(self): 测试产品创建视图 data { name: 新测试产品, price: 199.99, stock: 50 } response self.client.post(reverse(product-create), data) self.assertEqual(response.status_code, 302) # 重定向 self.assertTrue(Product.objects.filter(name新测试产品).exists())5.3 API测试from rest_framework.test import APITestCase from rest_framework import status class ProductAPITest(APITestCase): def setUp(self): self.product Product.objects.create( nameAPI测试产品, price299.99, stock200 ) def test_product_list_api(self): 测试产品列表API response self.client.get(/api/products/) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 1) def test_product_detail_api(self): 测试产品详情API url f/api/products/{self.product.id}/ response self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data[name], API测试产品)6. 测试优化策略6.1 测试数据工厂使用factory_boy创建测试数据pip install factory_boy创建工厂类import factory from myapp.models import Product class ProductFactory(factory.django.DjangoModelFactory): class Meta: model Product name factory.Faker(word) price factory.Faker(pydecimal, left_digits3, right_digits2, positiveTrue) stock factory.Faker(random_int, min0, max1000)在测试中使用def test_with_factory(): product ProductFactory() assert product.price 06.2 测试性能优化使用setUpTestData替代setUpclass TestPerformance(TestCase): classmethod def setUpTestData(cls): 整个测试类只执行一次 cls.product ProductFactory.create_batch(100) def test_performance(self): 性能测试 # 使用预先创建的数据 pass使用事务加速测试from django.test import TransactionTestCase class FastTest(TransactionTestCase): 对于需要测试事务行为的用例使用6.3 持续集成配置示例GitLab CI配置.gitlab-ci.ymlstages: - test unit_test: stage: test image: python:3.9 before_script: - pip install -r requirements.txt script: - python manage.py test --noinput - coverage run -m pytest - coverage xml artifacts: reports: cobertura: coverage.xml only: - merge_requests - master7. 常见问题与解决方案7.1 测试数据库问题问题测试数据库未正确重置解决确保使用TransactionTestCase或添加--keepdb参数python manage.py test --keepdb7.2 测试依赖问题问题测试执行顺序影响结果解决确保每个测试都是独立的使用setUp创建干净环境7.3 慢速测试优化方案使用Mock替代外部API调用减少数据库操作并行运行测试pip install pytest-xdist pytest -n auto7.4 测试失败诊断当测试失败时检查测试数据是否正确环境变量是否设置模拟对象行为是否符合预期时间相关测试是否考虑时区使用--pdb调试失败测试python -m pytest --pdb8. 测试最佳实践命名规范测试模块test_*.py测试类Test* 或 *TestCase测试方法test_*测试结构def test_method_should_do_something_when_condition(self): # 准备 (Arrange) obj ClassUnderTest() # 执行 (Act) result obj.method() # 断言 (Assert) self.assertEqual(expected, result)测试原则每个测试只验证一件事避免测试实现细节测试应该稳定可靠测试应该快速执行测试金字塔单元测试70%集成测试20%E2E测试10%测试文档为复杂测试添加docstring使用有意义的断言消息记录测试的设计决策在实际项目中我通常会建立一个tests目录结构如下tests/ ├── unit/ │ ├── models/ │ ├── services/ │ └── utils/ ├── integration/ │ ├── api/ │ └── workflows/ └── e2e/ ├── ui/ └── api/这种结构可以清晰地组织不同层次的测试便于团队协作和维护。对于大型项目建议将测试与业务代码分离但保持相同的包结构这样既保持了内聚性又避免了测试代码污染生产代码。