Django单元测试

为什么需要自动化测试

  • 对于大项目,对每个单元进行测试可以快速定位错误,确保项目质 量
  • 在 python 中,利用 python 的测试化模块搭建一些测试不会比手 动测试一些数据更麻烦
    • 多写一些代码
    • (几乎)不和终端的输入输出打交道
    • 少和单步调试打交道
  • 在 python 交互式终端的输入输出,可以马上成为一些测试样例
  • 带有和 Java 类似的单元测试框架,可以提取出不同测试用例的相 同步骤
  • 回归测试:一个测试用例可以在开发过程中反复测试,保证加入新 功能后不会妨碍原有功能的运转

测试覆盖率

  • 定义:在运行测试时运行的代码占总代码量的比例
  • 作用:找出没有被测试过的函数和代码行
  • 自带 trace.py:python -m trace --help
    • --count 统计每行被执行的次数
    • --trace 程序每执行一行,就将这一行打印到标准输出
    • 统计被执行的函数名、调用关系、累积多次运行的总次数、标出没有被运行的代码行⋯⋯

Doctest

  • python 的 docstring 中有很多在交互式终端中运行的例子
  • doctest 可以检查 docstring 中样例的正确性
  • 同样的,可以把手动测试的终端输出放进 docstring 里,变成一个 测试用例
  • 注意:一般小型的或者简单的功能可以使用,但是较大的项目或者较复杂的功能并不推荐使用doctest

下面是一个样例

def factorial(n): 
"""Return the factorial of n, an exact integer >= 0.

>>> [factorial(n) for n in range(6)] 
[1, 1, 2, 6, 24, 120] 
>>> factorial(30) 265252859812191058636308480000000 
>>> factorial(-1) 
Traceback (most recent call last): 
    ...
 ValueError: n must be >= 0 
"""

import math 
if not n >= 0: 
    raise ValueError("n must be >= 0") 
result = 1 
factor = 2 
while factor <= n: 
    result *= factor 
    factor += 1 
return result
if __name__ == "__main__": 
    import doctest 
    doctest.testmod()

Unittest

  • 基于 JUnit 测试框架 (JAVA)
  • 比 doctest 更加灵活, 功能更加强大
  • 我们要提到的django单元测试就是基于unittest的,下面先简要介绍下如何使用unittest

如何使用Unittest

  • 先构造 unittest.TestCase 的子类
  • unittest.main() 用法
    • unittest.main() 函数将会实例化所有当前 py 文件下的 TestCase 的 子类并且执行以’test’ 开头的函数
    • 若需要跨文件测试,则只需 import 含 testBase 子类的 py 文件再调 用 unittest.main() 即可
  • startUp 与 tearDown 函数
    • startUp 将会在每个 test 函数执行前被调用
    • tearDown 将会在每个 test 函数执行后被调用
    • 通常用来初始化以及清除测试过程中的一些代码
  • 常用的TestCase函数:
    • assert_(expr[, msg]) 与 failUnless(expr[, msg])
      • 会对不正确的结果报错
    • assertEqual(x, y[, msg]) 与 failUnlessEqual(x, y[, msg])
      • 值不等时报错并输出二者的值
    • assertAlmostEqual(x, y[, places[, msg]]) 与 failUnlessAlmostEqual(x, y[, places[, msg]])
      • 值不近似等时报错,用于浮点数的判等
    • assertRaises(exc, callable, ...) 与 failUnlessRaises(exc, callable, ...)
      • 该回调函数没有抛出 exc 的异常则报错

下面是一个样例

class ProductTestCase(unittest.TestCase): 
    def testIntegers(self): 
        x = 3 
        y = 4 
        p = my_math.product(x, y) 
        self.failUnless(p == x*y, 'Integer␣multiplication␣failed') 
if __name__ == '__main__': unittest.main()
*************************Wrong************************* 
F 
======================================================== 
FAIL: testIntegers (__main__.ProductTestCase) 
-------------------------------------------------------
Traceback (most recent call last): 
                File "test_my_math.py", line 17, in testIntegers self.failUnless(p == x*y, 'Integer␣multiplication␣ failed') 
AssertionError: Integer multiplication failed 
-------------------------------------------------------
Ran 1 tests in 0.001s 
FAILED (failures=1)

*************************Correct************************* 
. 
-------------------------------------------------------
Ran 1 tests in 0.001s 
OK
  • 当测试结果正确时,返回一个.而不正确时是返回一个F

源码检查

  • PyChecker
    • 检查 python 源码错误,例如传参错误,没有导入模块,同一作用域 中重定义函数、类方法等。
    • 使用:pychecker [options] file1.py file2.py ...
  • PyLint
    • 支持大部分 PyChecker 功能
    • 更强大的功能,例如检查变量名是否符合规定,检查一行代码的长 度,一个声明过的接口是否被实现
    • 使用:pylint [options] module_or_package
  • 结合 unittest 使用
    • 代码中嵌入
    • PyChecker 与 PyLint 集成到 IDE

Django单元测试

讲了这么多python的单元测试,那如何进行Django的单元测试呢?

  • 在Django的单元测试中,仍然推荐使用python的unittest模块,像我们刚刚在上面提到的使用方法,使用类名为django.test.TestCase,继承于python的unittest.TestCase。
class TestDefault(TestCase):

    def setUp(self):
        # 设置配置
        settings.IGNORE_WECHAT_SIGNATURE = True

        # user1 => not bind
        # user2 => bind
        User.objects.create(open_id='abc')
        User.objects.create(open_id='a', student_id='2016013265')

        # textMsgs => 用户一般可能输入(成功)
        self.textMsgs = ['balabala', 'gg', '抢火车票']

    # 是否返回帮助
    def is_default(self, content):
        pattern = '对不起,没有找到您需要的信息:('
        return content.find(pattern) != -1

    def test_text(self):
        users = User.objects.all()

        for user in users:
            for textMsg in self.textMsgs:
                fromUser = user.open_id
                curTime = str(getTimeStamp(datetime.datetime.now()))
                msgId = str(random.randint(0, 99999)) + curTime
                data = getTextXml(fromUser, curTime, textMsg, msgId)

                response = self.client.post(
                    path='/wechat/',
                    content_type='application/xml',
                    data=data
                )

                content = str(response.content.decode('utf-8'))
                self.assertEqual(self.is_default(content), True)

  • 执行目录下所有的测试(所有的test*.py文件):

python manage.py test

运行测试的时候,测试程序会在所有以test开头的文件中查找所有的test cases(inittest.TestCase的子类),自动建立测试集然后运行测试。(注意是所有app的test文件,包括子目录中的)

  • 执行项目的所有的test测试:

python manage.py test

运行结果:


image.png
  • 执行项目某个下tests包里的测试:

python manage.py xxx.tests

  • 单独执行某个test case:

python manage.py xxx.TestDefault

  • 单独执行某个测试方法:

python manage.py xxx.TestDefault.testxxx/py

  • 通配测试文件名:

python manage.py test–pattern=”tests_*.py”

  • 启用warnings提醒:

python -Wall manage.py test

数据库

测试是需要数据库的,django会为测试单独生成数据库。不管你的测试是否通过,当你所有的测试都执行过后,这个测试数据库就会被销毁。

  • 默认情况下,测试数据库的名字是test_DATABASE_NAME,DATABASE_NAME是你在settings.py里配置的数据库名;
    如果你需要给测试数据库一个其他的名字,在settings.py中指定TEST_DATABASE_NAME的值
  • 使用sqlite3时,数据库是在内存中创建的。
    除了数据库是单独创建的以外,测试工具会使用相同的数据库配置--DATABASE_ENGINE, DATABASE_USER, DATABASE_HOST等等.
    创建测试数据库的用户DATABASE_USER(settings中)指定,所以你需要确认 DATABASE_USER有足够的权限去创建数据库。

为了保证所有的测试都从干净的数据库开始,执行顺序如下:

  • 1.所有的TestCase子类首先运行。
  • 2.所有其他的单元测试(unittest.TestCase,SimpleTestCase,TransactionTestCase)。
  • 3.其它的测试(例如doctests等)

加速测试

  • 可以将PASSWORD_HASHERS设置为更快的算法:
PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

travis CI与django的单元测试

  • 在.travis.yml文件中加入(修改):
script: 
    - python manage.py test

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容