Django单元测试
作者:Ljian1992
链接:https://www.jianshu.com/p/34267dd79ad6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关资源
简单的例子
from django.test import TestCase from myapp.models import Animal # Django的单元测试基于unittest库 class StudentTestCase(TestCase): # 测试函数执行前执行 def setUp(self): print("======in setUp") # 需要测试的内容 def test_add(self): student = Student(name='aaa') student.save() self.assertEqual(student.name, 'aaa') # 需要测试的内容 def test_check_exit(self): self.assertEqual(0, Student.objects.count()) # 测试函数执行后执行 def tearDown(self): print("======in tearDown")
关于django的单元测试,需要知道的是
- 对于每一个测试方法都会将setUp()和tearDown()方法执行一遍
- 会单独新建一个测试数据库来进行数据库的操作方面的测试,默认在测试完成后销毁。
- 在测试方法中对数据库进行增删操作,最后都会被清除。也就是说,在test_add中插入的数据,在test_add测试结束后插入的数据会被清除。
- django单元测试时为了模拟生产环境,会修改settings中的变量,例如, 把DEBUG变量修改为True, 把ALLOWED_HOSTS修改为[*]。
运行单元测试
当您运行测试时,测试实用程序的默认行为是在任何名称以test开头的文件中找到所有的测试用例(即unittest.TestCase的子类),然后从这些测试用例中自动构建一个测试套件,并运行该套件。
在单元测试中,可以指定测试粒度。这样可以专注于只测试还没测试的单元测试,而已经测试过的就不测试了。
# 测试整一个工程 $ ./manage.py test # 只测试某个应用 $ ./manage.py test app --keepdb # 只测试一个Case $ ./manage.py test animals.tests.StudentTestCase # 只测试一个方法 $ ./manage.py test animals.tests.StudentTestCase.test_add
一些常见问题的解决
数据表多时创建数据库销毁过多时间
在单元测试时,若migrations的文档过多时,每次单元测试时间绝大部分都消耗在数据库的创建。试过,单元测试代码运行只要几十秒,而数据库的创建却用去了十分钟。这是个让人绝望的速度,万幸的是django有提供命令使用进行单元测试过后不删除数据库。 这个命令就是: --keepdb
指定测试数据库的默认字符集
在创建测试数据库时,数据库的默认字符集也许不是我们想要的例如latin1。可以通过在数据库配置中指定TEST_CHARSET, TEST_COLLATION 参数,来指定字符集以及排序规则
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'xxxx', 'USER': 'xxxx', 'PASSWORD': '', 'HOST': 'localhost', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', 'init_command': 'SET default_storage_engine=INNODB', }, 'TEST_CHARSET': 'utf8', 'TEST_COLLATION': 'utf8_general_ci', }, }
** settings变量的修改**
若干需要在单元测试时修改,setting命令。例如,django在单元测试时会将settings.DEBUG 设置为True, 而我们需要将其设置为False
方式1: 直接在修改
class BaseApiTest(TestCase): def setUp(self): #testcase DEBUG = False settings.DEBUG = False def test_b(self): self.assertEqual(2, 1+1) def tearDown(self): pass
方式2: 通过装饰器修改
from django.test.utils import override_settings class BaseTest(TestCase): def setUp(self): pass # 利用该装饰器,可以在但个测试函数内修改settings变量, 而不影响 @override_settings(DEBUG=False) def test_b(self): self.assertEqual(2, 1+1) def tearDown(self): pass
API权限问题的解决
在测试API的时候,往往需要等等进行用户登录才有权限调用,此时需要指定登录用户来解决接口调用的权限问题
# 如果是API使用了rest_framework框架 from rest_framework.test import APIClient class BaseTest(TestCase): def setUp(self): # 创建一个用户 self.user = create_user(uuid4().hex, '123456789') self.client = APIClient() # 通过force_authenticate函数来执行用户 self.client.force_authenticate(self.user) def test_b(self): self.assertEqual(2, 1+1) def tearDown(self): pass
Celery异步任务的测试
在代码中几乎肯定是会有celery异步任务,若想对异步任务进行单元测试。可以将CELERY_ALWAYS_EAGER=True, BROKER_BACKEND='memory'
from xxx.celery import app @app.task(bind=True) def add(self,x, y): return x + y class TaskTest(TestCase): def setUp(self): settings.CELERY_ALWAYS_EAGER = True def test_add(self): self.assertEqual(2, add.apply_async((1,1))) def tearDown(self): pass
单元的等级(来自知乎的gashero)
在知乎上看到gashero根据经验总结出来的单元测试总结,非常认同。根据功能的重要性,来进行不同程度的测试。
- Level1:正常流程可用,即一个函数在输入正确的参数时,会有正确的输出
- Level2:异常流程可抛出逻辑异常,即输入参数有误时,不能抛出系统异常,而是用自己定义的逻辑异常通知上层调用代码其错误之处
- Level3:极端情况和边界数据可用,对输入参数的边界情况也要单独测试,确保输出是正确有效的
- Level4:所有分支、循环的逻辑走通,不能有任何流程是测试不到的
- Level5:输出数据的所有字段验证,对有复杂数据结构的输出,确保每个字段都是正确的地方