263 lines
7.0 KiB
Markdown
263 lines
7.0 KiB
Markdown
# 测试规范
|
|
|
|
## 概述
|
|
|
|
项目采用 Django 内置测试框架(基于 `unittest`),支持单元测试和集成测试。测试使用 `python manage.py test` 命令运行。
|
|
|
|
:::tip 测试体系
|
|
1. 单元测试(Unit):验证单个函数/方法的逻辑正确性
|
|
2. 集成测试(Integration):验证模块间协作、真实数据库操作
|
|
:::
|
|
|
|
## 目录结构
|
|
|
|
```
|
|
tests/
|
|
├── test_models.py # 模型测试
|
|
├── test_services.py # Service 层测试
|
|
├── test_views.py # View 层测试
|
|
├── test_forms.py # Form 层测试
|
|
└── test_utils.py # 工具函数测试
|
|
```
|
|
|
|
或按应用组织:
|
|
|
|
```
|
|
application/example/
|
|
├── models.py
|
|
├── services.py
|
|
├── views.py
|
|
├── forms.py
|
|
└── tests.py # 该应用的测试
|
|
```
|
|
|
|
## Django TestCase 示例
|
|
|
|
### 模型测试
|
|
|
|
```python
|
|
from django.test import TestCase
|
|
from application.example.models import Example
|
|
|
|
|
|
class ExampleModelTest(TestCase):
|
|
"""案例模型测试"""
|
|
|
|
def test_create_example(self):
|
|
"""测试创建案例"""
|
|
example = Example.objects.create(
|
|
name='测试案例',
|
|
type=1,
|
|
status=1,
|
|
sort=1,
|
|
create_user='admin'
|
|
)
|
|
self.assertIsNotNone(example.id)
|
|
self.assertEqual(example.name, '测试案例')
|
|
self.assertFalse(example.is_delete)
|
|
|
|
def test_soft_delete(self):
|
|
"""测试软删除"""
|
|
example = Example.objects.create(
|
|
name='待删除案例',
|
|
type=1,
|
|
status=1,
|
|
sort=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()
|
|
)
|
|
```
|
|
|
|
### Service 层测试
|
|
|
|
```python
|
|
from django.test import TestCase, RequestFactory
|
|
from application.example.services import get_example_page, add_example
|
|
|
|
|
|
class ExampleServiceTest(TestCase):
|
|
"""案例 Service 层测试"""
|
|
|
|
def setUp(self):
|
|
"""测试前准备"""
|
|
self.factory = RequestFactory()
|
|
# 创建测试数据
|
|
Example.objects.create(name='案例1', type=1, status=1, sort=1)
|
|
Example.objects.create(name='案例2', type=1, status=1, sort=2)
|
|
|
|
def test_get_example_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_get_example_page_with_filter(self):
|
|
"""测试带筛选条件的分页查询"""
|
|
request = self.factory.get('/example/page', {'name': '案例1'})
|
|
result = get_example_page(request)
|
|
self.assertEqual(result.status_code, 200)
|
|
```
|
|
|
|
### View 层测试
|
|
|
|
```python
|
|
from django.test import TestCase, Client
|
|
from application.example.models import Example
|
|
|
|
|
|
class ExampleViewTest(TestCase):
|
|
"""案例 View 层测试"""
|
|
|
|
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')
|
|
# 未登录应返回 401 或重定向
|
|
self.assertIn(response.status_code, [401, 302])
|
|
|
|
def test_page_with_login(self):
|
|
"""测试登录后访问分页接口"""
|
|
# 模拟登录(需根据实际认证方式调整)
|
|
self.client.force_login(user=User.objects.first())
|
|
response = self.client.get('/example/page')
|
|
self.assertEqual(response.status_code, 200)
|
|
```
|
|
|
|
### Form 层测试
|
|
|
|
```python
|
|
from django.test import TestCase
|
|
from application.example.forms import ExampleForm
|
|
|
|
|
|
class ExampleFormTest(TestCase):
|
|
"""案例 Form 层测试"""
|
|
|
|
def test_valid_form(self):
|
|
"""测试有效表单"""
|
|
data = {
|
|
'name': '测试案例',
|
|
'type': 1,
|
|
'status': 1,
|
|
'sort': 1,
|
|
}
|
|
form = ExampleForm(data)
|
|
self.assertTrue(form.is_valid())
|
|
|
|
def test_invalid_form_missing_name(self):
|
|
"""测试缺少必填字段"""
|
|
data = {
|
|
'type': 1,
|
|
'status': 1,
|
|
'sort': 1,
|
|
}
|
|
form = ExampleForm(data)
|
|
self.assertFalse(form.is_valid())
|
|
self.assertIn('name', form.errors)
|
|
|
|
def test_invalid_form_type_out_of_range(self):
|
|
"""测试类型值超出范围"""
|
|
data = {
|
|
'name': '测试案例',
|
|
'type': 99, # 超出范围
|
|
'status': 1,
|
|
'sort': 1,
|
|
}
|
|
form = ExampleForm(data)
|
|
self.assertFalse(form.is_valid())
|
|
self.assertIn('type', form.errors)
|
|
```
|
|
|
|
## 测试命名规范
|
|
|
|
| 类型 | 文件命名 | 函数命名 |
|
|
|------|---------|---------|
|
|
| 模型测试 | `test_*.py` | `test_*` |
|
|
| Service 测试 | `test_*.py` | `test_*` |
|
|
| View 测试 | `test_*.py` | `test_*` |
|
|
| Form 测试 | `test_*.py` | `test_*` |
|
|
|
|
:::tip 函数命名规则
|
|
测试函数名应清晰描述被测行为:
|
|
- `test_create_example`:验证创建案例
|
|
- `test_soft_delete`:验证软删除
|
|
- `test_get_page_with_filter`:验证带筛选的分页查询
|
|
:::
|
|
|
|
## 运行命令
|
|
|
|
```bash
|
|
# 运行所有测试
|
|
python manage.py test
|
|
|
|
# 运行指定应用的测试
|
|
python manage.py test application.example
|
|
|
|
# 运行指定测试文件
|
|
python manage.py test application.example.tests
|
|
|
|
# 运行指定测试类
|
|
python manage.py test application.example.tests.ExampleModelTest
|
|
|
|
# 运行指定测试方法
|
|
python manage.py test application.example.tests.ExampleModelTest.test_create_example
|
|
|
|
# 显示详细输出
|
|
python manage.py test -v 2
|
|
|
|
# 运行测试并显示覆盖率
|
|
python manage.py test --parallel
|
|
```
|
|
|
|
## 测试数据库
|
|
|
|
Django 测试框架会自动创建测试数据库(命名为 `test_{DATABASE_NAME}`),测试完成后自动销毁。
|
|
|
|
:::tip 测试数据库配置
|
|
- 测试数据库与生产数据库完全隔离
|
|
- 每个测试方法运行在独立事务中,测试结束后自动回滚
|
|
- 使用 `TestCase` 而非 `TransactionTestCase` 可获得更好的性能
|
|
:::
|
|
|
|
## 测试覆盖率
|
|
|
|
```bash
|
|
# 安装覆盖率插件
|
|
pip install coverage
|
|
|
|
# 运行测试并生成覆盖率报告
|
|
coverage run --source='application' manage.py test
|
|
|
|
# 查看报告
|
|
coverage report
|
|
|
|
# 生成 HTML 报告
|
|
coverage html
|
|
```
|
|
|
|
:::tip 覆盖率目标
|
|
- Model 层:90%+
|
|
- Service 层:80%+
|
|
- View 层:70%+
|
|
:::
|
|
|
|
## 总结
|
|
|
|
测试规范使用 Django 内置测试框架,支持模型、Service、View、Form 各层测试。测试文件命名 `test_*.py`,函数命名 `test_*`。使用 `python manage.py test` 运行测试,测试数据库自动创建和销毁。建议使用 `coverage` 工具监控测试覆盖率。
|