Django Testing
Django 自己的 unittest 支持的挺好,一般只需要在 app 下面加一个 tests.py 在里面写 case 就可以了。case 对应的类继承 django.test.TestCase
就好。
这个 django.test.TestCase
继承自 unittest.TestCase
,django 这个多了一个自动使用事务的功能,所以用 django 这个的话,每个测试用例执行前后会自动回滚数据库操作,这样不用你自己 cleanup 数据,还比较方便。
setUp 和 tearDown
每一个测试类里面,都可以有一个 setUp
方法,是在 case 方法执行前执行,例如一些准备工作,和一个 tearDown
方法,在 case 执行之后执行,例如一些清理工作。还可以有若干个使用 test_
开头的测试用例,这些 setUp
其实类似于把每个测试用例里面共同的部分提取出来一样,不过是不用你在每个 case 里面单独调用了,会自动处理。
setUpClass 和 tearDownClass
django 还提供了 setUpClass
和 tearDownClass
,类似上面的 setUp
方法,不过这个是每个 class 只会执行一次。另外按照这里的说法, setUpClass
不会使用事务,不过我看源码(django 2.1.4) 好像是会的,我没测试。。
@classmethod
def setUpClass(cls):
super().setUpClass()
if not connections_support_transactions():
return
cls.cls_atomics = cls._enter_atomics()
if cls.fixtures:
for db_name in cls._databases_names(include_mirrors=False):
try:
call_command('loaddata', *cls.fixtures, **{'verbosity': 0, 'database': db_name})
except Exception:
cls._rollback_atomics(cls.cls_atomics)
raise
try:
cls.setUpTestData()
except Exception:
cls._rollback_atomics(cls.cls_atomics)
raise
代码可以看到,还有一个 setUpTestData
可以用。如果只是准备数据库数据的话,感觉后面这个更精准一点。
测试用例
case 我觉得一般可以分两种,方法测试,和接口测试。
方法测试指针对一些工具方法什么的测试,当然这个说法并不严谨,将就理解吧。我把这些归类为不涉及到数据库操作的测试。
接口测试,一般会涉及到数据库操作,需要验证登录啊,参数什么的。
Django 里面,每个测试用例之间是通过事务互相隔离的,所以不用担心互相之间会有影响。
接口测试可以通过 django.test.Client
来访问你的接口,然后比对返回结果,或者比对数据库的数据来验证。
fixtures
有时候一些接口是依赖已有数据的,比如一个返回所有用户的接口,那测试的时候数据库是需要有用户才能返回的。这个可以通过 fixture 来 moke 数据。
fixture
就是一些 json 文件,里面放的是和 model 的数据,这样一个测试如果需要某几个 model 对应的表里面事先有数据,那可以把他们放到 fixture 文件里面,让 django 在运行之前先 load 到数据库就可以了。
这些 json 文件自己编写会死,Django 提供了 manage.py dumpdata --indent 4 [app_label[.ModelName] [app_label[.ModelName]
功能,可以方便你导出数据库里面已有的数据。不指定 app_label 和 modelname 就会导出全部的,一般只导出自己需要的就好。注意 json 文件是可以支持缩进的。
如果从比如开发库之类的倒数据,会觉得数据有点乱,从测试库倒数据似乎比较清净,因为每次测试都是一个空的数据库。有一个方法是在测试用例里面创建依赖的数据,但是测试执行完了再执行 manage.py dumpdata
已经什么数据都没有了。这个时候可以在测试用例里面使用 django.core.management.call_command
来执行 dump,例如
call_command(
'dumpdata',
'--indent', '4',
'app_label', 'ModelName',
'app_label', 'ModelName'
)
这个其实类似前面 setUpClass
里面的加载 fixtures 的代码用了类似的方法 call_command('loaddata', *cls.fixtures, **{'verbosity': 0, 'database': db_name})
。
命令行执行测试的时候,会打印出来 dump 出来的数据,把他们存到一个 json 文件然后引入就可以了。
给测试提速
有一个提升测试速度的方法,是使用 -k
参数,这个参数会保留测试的数据库,不会每次都删除重建,这样节省一些时间。
还可以使用 --parallel N
参数来增加并行数量。如果你用 coverage 那整个命令是类似这样的 coverage run --parallel-mode --concurrency=multiprocessing manage.py test -k --parallel 3
。使用并行之后,会发现 coverage 不工作了,这是因为并行的时候,每个线程都会单独写一个 coverage 结果文件,所以执行 coverage report
之前,执行一下 coverage combine
合并到一个文件就可以了。
Django 还是做的挺不错的。测试这么方便,实际很适合使用 TDD 方式开发。
其他
看到一篇文章,从 TransactionTestCase
替换到 TestCase
速度提升了 3 倍。
Django 里面有三个测试类
SimpleTestCase
是最简单的。提供unittest.TestCase
基础的功能。默认屏蔽了数据库的访问,因为对数据库里面的修改没有隔离,所以应该在没有数据库操作的时候使用他。TransactionTestCase
继承自SimpleTestCase
,允许数据库操作,测试完毕之后会删除数据库里面的所有数据。会比较慢。TestCase
是我们平时用的。继承自TransactionTestCase
,使用事务来回滚所有操作,这样比遍历所有表快一点,这样你的操作也不能真实的提交,但是我们跑测试的时候一般也不需要提交。
TransactionTestCase
允许你的代码自己使用和管理事务, TestCase
自己使用了事务。