Become a sponsor

说明
本文在 测试规范 基础上,介绍更高级的测试技巧,包括 Mock、Fixture、API 测试、覆盖率实战等。
Mock 用于模拟外部依赖,隔离被测代码:
# 不使用 Mock:测试依赖 Redis 连接
def test_get_captcha():
# 如果 Redis 未启动,测试会失败
captcha = generate_captcha() # 依赖 Redis 存储
# 使用 Mock:隔离 Redis 依赖
@patch('utils.redis.get_redis_connection')
def test_get_captcha(mock_redis):
mock_redis.return_value = MagicMock()
captcha = generate_captcha() # 不依赖真实 Redisfrom unittest.mock import patch, MagicMock
class UserServiceTest(TestCase):
"""用户服务测试"""
@patch('application.user.services.get_redis_connection')
def test_login_with_mock_redis(self, mock_redis):
"""测试登录(Mock Redis)"""
# 配置 Mock
mock_redis.return_value.get.return_value = None
# 执行测试
result = login_service(request)
# 验证结果
self.assertEqual(result['code'], 0)
@patch('utils.common.get_client_ip')
def test_record_log_with_mock_ip(self, mock_ip):
"""测试记录日志(Mock IP 获取)"""
mock_ip.return_value = '127.0.0.1'
record_login_log(request, 'admin', 1, 0)
# 验证日志记录# Mock 返回固定值
mock_func.return_value = 'mocked_value'
# Mock 返回不同值(按调用顺序)
mock_func.side_effect = ['first', 'second', 'third']
# Mock 抛出异常
mock_func.side_effect = Exception('connection failed')
# Mock 根据参数返回不同值
def side_effect_func(arg):
if arg == 'valid':
return True
return False
mock_func.side_effect = side_effect_funcclass ExampleServiceTest(TestCase):
"""案例服务测试"""
def setUp(self):
"""测试前准备:创建测试数据"""
self.user = User.objects.create(
username='testuser',
realname='测试用户',
password=make_password('123456'),
status=1
)
self.example = Example.objects.create(
name='测试案例',
type=1,
status=1,
sort=1,
create_user='testuser'
)
def tearDown(self):
"""测试后清理(可选,Django TestCase 自动回滚)"""
passdef create_test_examples(count=10):
"""批量创建测试数据"""
examples = []
for i in range(count):
examples.append(Example(
name=f'案例{i}',
type=i % 3 + 1,
status=1,
sort=i,
create_user='admin'
))
Example.objects.bulk_create(examples)
return examples# pip install factory-boy
import factory
from application.example.models import Example
class ExampleFactory(factory.django.DjangoModelFactory):
"""案例工厂"""
class Meta:
model = Example
name = factory.Sequence(lambda n: f'案例{n}')
type = 1
status = 1
sort = factory.Sequence(lambda n: n)
create_user = 'admin'
# 使用
example = ExampleFactory() # 创建单个
examples = ExampleFactory.create_batch(10) # 批量创建
example = ExampleFactory(name='自定义名称') # 自定义字段from django.test import TestCase, Client
class ExampleViewTest(TestCase):
"""案例视图测试"""
def setUp(self):
self.client = Client()
# 创建测试数据
Example.objects.create(name='案例1', type=1, status=1, sort=1)
def test_page_without_login(self):
"""测试未登录访问"""
response = self.client.get('/example/page')
self.assertIn(response.status_code, [401, 302])
def test_page_with_login(self):
"""测试登录后访问"""
# 模拟登录
self.client.force_login(User.objects.first())
response = self.client.get('/example/page')
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data['code'], 0)
self.assertIn('records', data['data'])from django.test import TestCase, RequestFactory
class ExampleServiceTest(TestCase):
"""案例服务测试"""
def setUp(self):
self.factory = RequestFactory()
def test_get_page(self):
"""测试分页查询"""
request = self.factory.get('/example/page', {'pageNo': 1, 'pageSize': 10})
result = get_example_page(request)
self.assertEqual(result.status_code, 200)
def test_add(self):
"""测试添加"""
request = self.factory.post(
'/example/add',
data=json.dumps({'name': '新案例', 'type': 1}),
content_type='application/json'
)
result = add_example(request)
self.assertEqual(result.status_code, 200)class PermissionTest(TestCase):
"""权限测试"""
def test_admin_bypass_permission(self):
"""测试管理员跳过权限检查"""
admin = User.objects.get(id=1)
self.client.force_login(admin)
response = self.client.get('/example/page')
self.assertEqual(response.status_code, 200)
def test_user_permission_check(self):
"""测试普通用户权限检查"""
user = User.objects.create(username='normal_user')
self.client.force_login(user)
response = self.client.get('/example/page')
# 无权限时应返回 401
self.assertIn(response.status_code, [401, 403])from django.core.files.uploadedfile import SimpleUploadedFile
class UploadTest(TestCase):
"""文件上传测试"""
def test_upload_image(self):
"""测试上传图片"""
image = SimpleUploadedFile(
name='test.png',
content=b'\x89PNG\r\n...',
content_type='image/png'
)
response = self.client.post('/upload/uploadFile', {'file': image})
self.assertEqual(response.status_code, 200)class SoftDeleteTest(TestCase):
"""软删除测试"""
def test_soft_delete(self):
"""测试软删除"""
example = Example.objects.create(name='待删除', type=1, status=1)
# 软删除
example.is_delete = True
example.save()
# 验证查询不到
self.assertFalse(
Example.objects.filter(is_delete=False, id=example.id).exists()
)
# 验证包含已删除可以查到
self.assertTrue(
Example.objects.filter(id=example.id).exists()
)# 安装
pip install coverage
# 运行测试并生成覆盖率
coverage run --source='application' manage.py test
# 查看报告
coverage report
# 生成 HTML 报告
coverage html
# 查看未覆盖的行
coverage report --show-missing| 层级 | 目标 | 说明 |
|---|---|---|
| Model 层 | 90%+ | 模型方法、字段约束 |
| Service 层 | 80%+ | 业务逻辑、异常处理 |
| View 层 | 70%+ | 请求处理、权限校验 |
| 工具函数 | 90%+ | 通用工具函数 |
# .github/workflows/test.yml
- name: Run tests
run: |
coverage run --source='application' manage.py test
coverage report --fail-under=80application/
├── user/
│ ├── models.py
│ ├── services.py
│ ├── views.py
│ └── tests.py # 该模块的测试
├── role/
│ └── tests.py
└── example/
└── tests.pytests/
├── test_models.py # 模型测试
├── test_services.py # Service 层测试
├── test_views.py # View 层测试
├── test_forms.py # Form 层测试
└── test_utils.py # 工具函数测试测试最佳实践涵盖 Mock/patch(隔离外部依赖)、Fixture(测试数据管理)、API 测试(Client/RequestFactory)、常见场景(权限/上传/软删除)、覆盖率(coverage + CI 集成)等方面。核心原则:隔离依赖、覆盖关键路径、自动化运行。