- Django 测试的教程:https://docs.djangoproject.com/zh-hans/3.1/intro/tutorial06/
- Django 测试的文档:https://docs.djangoproject.com/zh-hans/3.1/topics/testing/
在后端开发中,自己写测试样例还是非常重要的,不然每次修改程序以后手动测试,工作量又大,还很难测完整。
Django 项目中用到的测试主要是集成测试。
同作为 Web 框架的 Spring Boot 可以单元测试和集成测试,因为 Spring Boot 项目的分层很明显 (Controller, Service, DAO),可以对其中一层进行单元测试。
而 Django 框架本身已经实现了大部分功能 (DAO 由 Django Models 实现、Controller 由 Django Router 实现),只剩下 Service 业务逻辑部分需要做测试,所以直接集成测试就可以了。
Django runserver 时测试 API
DRF 教程提到,在 runserver
时手动测试看效果时可以使用 httpie
或者其他工具。但是 POST
数据似乎有点麻烦。我更常使用 Python 的 requests 库。
1 | pip install requests |
1 | from requests import get, post, put, delete |
语法和 django.test.client.Client
几乎一模一样。
Django test 环境初始化和清理
在编写一个 django.test.TestCase
类的每个函数时,可能涉及到某些重复步骤。如对活动内容进行测试前都需要创建一个活动。文档里提到,可以把相同的准备工作写为这个测试类的 setUp
方法,这个方法在每个测试函数之前都会被调用一次。
而与 setUp
对应的就是 tearDown
方法,它可以完成每个测试函数以后的清理工作(如清空邮箱、删除测试文件)。需要注意的是,一般来说不需要清理数据库,因为 Django 的每个测试函数都是一个事务,测试完成后会回滚。所以如果测试函数只修改了数据库,就不需要单独编写 tearDown
函数了。
1 | # 重置密码相关测试 |
setUp
和 tearDown
在每个函数执行前/后都会执行,而 setUpClass
和 tearDownClass
就是在测试类执行前/后执行。(注意 Django 也编写了 setUpClass
和 tearDownClass
,因此重写的时候,不要忘了 super().setUpClass()
)
Django test 测试邮件服务
Django test 还会替换掉默认的 SMTP 服务器,改为一个虚拟的、不会真正发送邮件的服务器。
文档:https://docs.djangoproject.com/zh-hans/3.1/topics/testing/tools/#email-services
官方也给了一个读取发件箱的方法,这样每次测试的时候就不用人工查询邮件,而是直接在测试代码里读取邮件信息,再配合正则表达式就可以提取出需要的信息了。下面是一个示例:
1 | from django.core import mail |
上面这个函数自动抓取发送邮件中的 token=XXXXX
字段中的 XXXXX
,保存到 token
变量然后返回。
Django test mock 当前时间
我在写签到的 TestCase 的时候,想要修改 now()
时间来进行测试。Google 了一下找到了 mock 的几种写法,这里演示一种(源代码):
1 | from unittest import mock |
对于整个类的每个测试函数都需要 mock 的情况,可以参考 Applying the same patch to every test method - unittest.mock。
mock 的对象是类和函数,如果想修改变量,直接赋值修改就可以了,不需要 mock。
Django test 和 Integration Error?
我在写登录的 TestCase 时出现了很奇怪的现象:正常运行时 API 貌似没有问题,在一个 Test 函数中调用一次 login
函数也没有问题,但如果调用两次 login
函数,Python
解释器会不报错而停止,错误码为 -1073741819 (0xC0000005)
。login()
函数如下:
1 | def login(request): |
test 函数如下:
1 | class LogInTest(TestCase): |
我参考了 Django 文档的 事务 部分,按照官方推荐的方法编写这段代码,但是出了问题。
个人猜测可能是 TestCase 中涉及的数据库回滚和 IntegrityError
触发回滚的冲突?
最终我只能按照 if
的方法替代掉 try-catch
的方法。尽量不要触发 IntegrityError
吧。
Django test 时,POST 和 PATCH 记得添加 content_type=’application/json’
笔者已经两次被这个坑了。第一次是在测试 PATCH 时,使用 django.test.client.Client.patch(path, data)
,返回的 HTTP 状态码为 415 Unsupported media type "application/octet-stream" in request.'
:
添加参数 Client.patch(path, data, content_type='application/json')
就好了。
Getting 415 code with django.test.Client’s patch method
后来,在 POST 的时候莫名其妙发现我写的下面这段 JSON,手动 POST 时能正常工作,但使用 Client.post(path, data)
时,嵌套的 {"id":1}
部分不能被正确识别到。
1 | { |
DEBUG 的时候注意到,response 中包含的 wsgi_request
里面,{"id":1}
就没有被正确提交。猜测可能是 Django Client 没有以 JSON 的形式解析这段代码,于是加上 content_type='application/json'
,就返回 201 Created
了。