# Django 单元测试完全指南 —— 从基础到高级的测试框架实战
·
一、测试环境搭建
1. 配置测试设置
# settings_test.py (继承主配置)
from .settings import *
# 使用内存数据库加速
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:' # 内存数据库
}
}
# 禁用密码哈希加速
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# 关闭调试模式
DEBUG = False
TEMPLATE_DEBUG = False
2. 核心依赖安装
pip install pytest pytest-django pytest-mock coverage
二、测试文件结构规范
myapp/
├── tests/
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_views.py
│ ├── test_forms.py
│ ├── test_utils/
│ │ ├── test_validators.py
│ ├── factories.py # 测试数据工厂
├── models.py
└── views.py
三、五大核心测试类型
1. 模型测试 (test_models.py)
from django.test import TestCase
from .models import Product
from .factories import ProductFactory
class ProductModelTest(TestCase):
@classmethod
def setUpTestData(cls):
# 创建一次供所有测试使用
cls.product = ProductFactory(name="Test Product")
def test_name_label(self):
field_label = self.product._meta.get_field('name').verbose_name
self.assertEqual(field_label, 'product name')
def test_price_max_digits(self):
max_digits = self.product._meta.get_field('price').max_digits
self.assertEqual(max_digits, 10)
def test_get_absolute_url(self):
url = self.product.get_absolute_url()
self.assertEqual(url, f'/products/{self.product.id}/')
2. 视图测试 (test_views.py)
from django.urls import reverse
from rest_framework.test import APITestCase
class ProductViewTest(APITestCase):
def setUp(self):
self.product = ProductFactory()
self.list_url = reverse('product-list')
self.detail_url = reverse('product-detail', args=[self.product.id])
def test_list_view_status_code(self):
response = self.client.get(self.list_url)
self.assertEqual(response.status_code, 200)
def test_detail_view_content(self):
response = self.client.get(self.detail_url)
self.assertContains(response, self.product.name)
def test_unauthorized_create(self):
data = {'name': 'New', 'price': 99.9}
response = self.client.post(self.list_url, data)
self.assertEqual(response.status_code, 401)
3. 表单测试 (test_forms.py)
from django.test import TestCase
from .forms import ProductForm
class ProductFormTest(TestCase):
def test_valid_data(self):
form = ProductForm(data={
'name': 'Valid Product',
'price': 50.00,
'category': 1
})
self.assertTrue(form.is_valid())
def test_invalid_price(self):
form = ProductForm(data={'name': 'Test', 'price': -10})
self.assertFalse(form.is_valid())
self.assertIn('price', form.errors)
4. API测试 (DRF)
from rest_framework import status
from .factories import UserFactory
class ProductAPITest(APITestCase):
def setUp(self):
self.user = UserFactory()
self.client.force_login(self.user)
def test_create_product(self):
data = {"name": "API Product", "price": 199.9}
response = self.client.post('/api/products/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['name'], "API Product")
5. 中间件/工具测试
from django.test import RequestFactory
from .middleware import CustomMiddleware
class MiddlewareTest(TestCase):
def test_custom_header(self):
factory = RequestFactory()
request = factory.get('/')
middleware = CustomMiddleware(lambda req: None)
middleware(request)
self.assertIn('X-Custom-Header', request.META)
四、高效测试数据管理
1. 使用Factories (factory_boy)
# factories.py
import factory
from .models import Product, User
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
username = factory.Sequence(lambda n: f'user_{n}')
email = factory.LazyAttribute(lambda u: f'{u.username}@example.com')
is_active = True
class ProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = Product
name = factory.Faker('word')
price = factory.Faker('pydecimal', left_digits=3, right_digits=2, positive=True)
owner = factory.SubFactory(UserFactory)
2. Fixtures 高级用法
# products.json
[
{
"model": "shop.Product",
"pk": 1,
"fields": {
"name": "Fixture Product",
"price": "99.99"
}
}
]
class TestWithFixtures(TestCase):
fixtures = ['products.json'] # 自动加载
def test_fixture_data(self):
product = Product.objects.get(pk=1)
self.assertEqual(product.price, Decimal('99.99'))
五、Mock 外部依赖
1. 模拟第三方 API
from unittest.mock import patch
from .services import PaymentService
class PaymentTest(TestCase):
@patch('myapp.services.requests.post') # 模拟requests
def test_payment_success(self, mock_post):
# 配置模拟响应
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {"status": "success"}
result = PaymentService.process(amount=100)
self.assertTrue(result.is_success)
2. 模拟时间
from freezegun import freeze_time
class TimeSensitiveTest(TestCase):
@freeze_time("2023-01-01")
def test_new_year(self):
from datetime import date
self.assertEqual(date.today(), date(2023, 1, 1))
六、测试覆盖率优化
1. 配置 coverage.py
# .coveragerc
[run]
source =
myapp/
omit =
*/migrations/*
*/tests/*
*/__init__.py
[report]
show_missing = true
fail_under = 85 # 低于85%失败
2. 执行测试并生成报告
coverage run -m pytest
coverage html # 生成HTML报告
七、测试运行优化
1. 并行测试
pytest -n auto # 自动使用所有CPU核心
2. 只运行失败用例
pytest --lf # 上次失败的用例
pytest --ff # 先运行失败用例
3. 标记慢测试
@pytest.mark.slow
def test_slow_operation():
time.sleep(5)
pytest -m "not slow" # 跳过慢测试
八、测试最佳实践
1. 测试金字塔策略
2. 核心原则
| 原则 | 说明 |
|---|---|
| FIRST原则 | Fast, Isolated, Repeatable, Self-Validating, Timely |
| Red-Green-Refactor | 测试驱动开发流程 |
| 最小化数据库交互 | 使用mock和内存数据库加速 |
| 单断言原则 | 每个测试验证一个行为 |
3. 常见缺陷规避
# 错误示范:
- 测试间存在依赖关系
- 包含业务逻辑的测试数据
- 过度测试实现细节
# 正确做法:
+ 每个测试独立可运行
+ 使用工厂创建测试数据
+ 关注接口行为而非内部实现
九、Django 测试框架对比
| 特性 | django.test | pytest |
|---|---|---|
| 用例组织 | Class-based | 函数式+Class |
| 参数化测试 | 需第三方库 | 内置支持 |
| Fixture 管理 | Fixtures | 内置+插件 |
| Mock 支持 | unittest.mock | 内置monkeypatch |
| 插件生态 | 有限 | 丰富 |
| 执行速度 | 较慢 | 快(并行) |
推荐组合:
pytest + pytest-django + factory_boy + coverage
完整示例项目:Django Testing Example
持续集成配置:GitHub Actions for Django
通过本指南,您将能够为Django应用构建完备的测试防护网,覆盖从模型到API的全栈功能,大幅提升代码质量和交付信心!
更多推荐
所有评论(0)