zoukankan      html  css  js  c++  java
  • Django 单元测试(简单例子)

    对于当今Web developer来说,自动化测试是一项非常有用的”除虫”工具。你可以使用测试集——test suite——去避免或者解决一系列的问题:
    当你在写新代码的时候,测试可以验证你的代码是否按预期执行
    当你重构或则修改旧代码时,你可以使用测试来确保你的修改不会对程序造成不良影响

    测试Web 程序是一项复杂的工作。因为一个Web程序由多种逻辑层组成——从Http层的request 执行,到表单的确定和处理,到模板的渲染。利用Django测试框架以及齐全的配套工具,
    你可以模request,插入测试数据,检查程序输出和一般情况下的测试。

    最重要的是,Django测试框架非常简单!

    Django中,测试由两种方式:doctests 和unit tests。

    doctests:
    doctests使用的是python中标准的doctest测试模块。它是由系统在你的docstring(注释)中查找测试代码。

    Django 的test runner在你的models.py文件中查找并执行你的doctests。如果找到tests.py文件,它同样会去里面查找doctests。
    你可以将doctest语句放入你工程中的任何models.py文件中。但一般情况下,会将app的doctests放在module docstring中,而model的doctests会放在各自的model中

    举例:

    from django.db import model
    class Animal(models.Model):
    """
    An animal that knows how to make noise
    # Create some animals
    >>> lion = Animal.objects.create(name="lion", sound="roar")
    >>> cat = Animal.objects.create(name="cat", sound="meow")
    # Make 'em speak
    >>> lion.speak()
    'The lion says "roar"'
    >>> cat.speak()
    'The cat says "meow"'
    """
    name = models.CharField(maxlength=20)
    sound = models.CharField(maxlength=20)
    def speak(self):
    return 'The %s says "%s"' % (self.name, self.sound)

    当你运行测试的时候,test utility会找到这个docstring。这里需要注意一下,上面的docstring有
    部分类似于python的交互环境。好了,运行它吧。

    unittests:
    Django的unit tests使用的标准模块库是unittest。和doctests一样,Django的test runner 在

    models.py文件或者在app目录下存在的tests.py文件中查找定义了的unit test cases。

    一个与上例doctests等价的unittest test case如下:
    import unittest
    from myapp.models import Animal
    class AnimalTestCase(unittest.TestCase):
    def setUp(self):
    self.lion = Animal.objects.create(name="lion", sound="roar")
    self.cat = Animal.objects.create(name="cat", sound="meow")
    def testSpeaking(self):
    self.assertEquals(self.lion.speak(), 'The lion says "roar"')
    self.assertEquals(self.cat.speak(), 'The cat says "meow"')

    当你运行tests的时候,test utility会找到models.py 和tests.py文件中所有的test case,在这些
    test case外自动构造一个test suite,并运行它。


    我应该用哪种?
    选择哪一个测试框架经常是有争议的,所以Django同时支持python的两种标注测试框架。选择哪一
    个取决于程序员个人的爱好。Django对于两种测试框架给与相同的支持。因为每一种测试系统都有自身
    的优点,所以最好的方法是将两者结合使用,使用对你测试最合适的测试系统。
    对于一个程序测试新手,这种选择可能会令人迷惑,所以这里给出了两种测试系统的一些诧异,以
    帮助你选择一个适合你的。
    如果你已经使用Python一段时间,doctest可能你会觉得doctest更Python一些。它让写测试尽
    量简单,(so there’s no overhead of writing classes or methods不知道咋翻译;()你只是简单
    的把测试代码放入了docstrings中。这样有一个附加的好处就是同时给modules提供了一份documentation
    ——好的doctests可以兼顾test和documentation,一石二鸟。

    如果你刚开始学习测试,那么doctests会让你快速入门。
    Unittest会让从java转过来的程序员觉得熟悉。因为unittest就是从Junit处获得灵感的。如果你
    有使用过类似于Junit测试框架的经验,那unittest会让你更习惯些。
    Unittest是根据class和method组织的,如果你在为一些类似的代码写一系列测试,那么你可以使
    用它的子集去抽象出它的共同任务。这样可以使你的代码小巧而整洁。它也提供直接的安装和卸载程序,
    可以使你在一个更高的层面上控制你的test case 运行的环境。
    重申,你可以并行的使用两种测试系统,甚至在同一个app中。大部分的项目都会用到这两种测试
    系统。两个测试系统都能找到更适合自己的位置。

    运行tests
    你可以使用下列语句运行你的测试
    $ ./manage.py test
    默认情况下,它会运行你项目里写在INSTALLED_APPS里所有app的测试. 如果你只想要测试其中某一个app,你比如说animals(假设你的INSTALLED_APPS里有两个app--myproject.polls和 myproject.animals) ,你可以运行以下
    命令来达到目的
    # ./manage.py test animals

    注意,我们使用了animals,而不是myproject.animals

    现在你可以(svn版本)针对更细力度的test class 或者 test method进行测试了.
    比如你的animals app 的测试单元里有AnimalTestCase测试类,这个类拥有testFluffyAnimals这个测试方法.那么,你可以通过
    $ ./manage.py test animals.AnimalTestCase
    来只运行AnimalTestCase这个测试类,通过
    $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
    来运行testFluffyAnimals这个测试方法.

    测试数据库
    测试运行需要的数据库并不需要你真实的数据库.一个单独的测试数据库会被创建.
    不管你的测试是否通过,当你所有的测试都执行过后,这个测试数据库就会被销毁.
    默认情况下,测试数据库的名字是test_DATABASE_NAME,DATABASE_NAME是你在settings.py里配置的数据库名.如果你需要给测试数据库一个其他的名字,在settings.py中指定TEST_DATABASE_NAME的值.
    除了使用一个单独的数据库外,测试工具会使用相同的数据库配置--DATABASE_ENGINE, DATABASE_USER, DATABASE_HOST等等.创建测试数据库的用户由DATABASE_USER(settings中)指定,所以你需要确认 DATABASE_USER
    有足够的权限去创建数据库.

    理解test输出的内容
    当你运行你的test后,你会看到一系列打印出来的信息.你可以在命令行中使用verbosity这个选项来控制输出信息的详细程度.

    Creating test database...
    Creating table myapp_animal
    Creating table myapp_mineral
    Loading 'initial_data' fixtures...
    No fixtures found.

    这段信息告诉你正在创建一个测试数据库.
    当创建测试数据库后,Django就会运行你的test.如果你的所有测试都通过了,你会看到以下信息,
    ----------------------------------------------------------------------
    Ran 22 tests in 0.221s

    OK

    如果有test没有通过,你会看到没有通过的test的细节:
    ======================================================================
    FAIL: Doctest: ellington.core.throttle.models
    ----------------------------------------------------------------------
    Traceback (most recent call last):
    File "/dev/django/test/doctest.py", line 2153, in runTest
    raise self.failureException(self.format_failure(new.getvalue()))
    AssertionError: Failed doctest test for myapp.models
    File "/dev/myapp/models.py", line 0, in models

    ----------------------------------------------------------------------
    File "/dev/myapp/models.py", line 14, in myapp.models
    Failed example:
    throttle.check("actor A", "action one", limit=2, hours=1)
    Expected:
    True
    Got:
    False

    ----------------------------------------------------------------------
    Ran 2 tests in 0.048s

    FAILED (failures=1)

    一份完整的错误信息输出超出了本文档的范围,但这会让你更直观;)你可以去查看python的unittest库了解更多细节.


    测试工具

    Django提供了一系列小工具用来写测试.
    Test Client
    test client是一个python类,来模拟一个简单的“哑”浏览器,允许你来测试你的view函数.
    你可以使用test client完成下列事情.
    模拟"Get"和"Post"请求,观察响应结果--从HTTP(headers,status codes)到页面内容.
    测试url执行了正确的view函数.
    测试一个request被Django模板渲染.

    Test Client无意于取代Twill,Selenium这些框架.Django有不同的专注点.

    概略
    使用test client,需要引入django.test.client.Client
    >>> from django.test.client import Client
    >>> c = Client()
    >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
    >>> response.status_code
    200
    >>> response = c.get('/customer/details/')
    >>> response.content
    '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 ...'

    如例所示,你需要实例化Client.

    test client运行机制
    test client并不需要有Web server.这是因为test client避开了HTTP的额外开销,直接处理Django框架.这样就可以是unit test运行飞快.

    当请求页面时需要指定url路径.不是整个域路径.
    这是对的
    >>> c.get('/login/')
    这是错的
    >>> c.get('http://www.example.com/login/')

    test client不适合操作不是由Django建立的网站.如果,你非要如此,请使用python的标准库--urllib或者urllib2.
    为了解析URl,test client使用由ROOT_URLCONF(settings.py)指定的URLconf
    尽管上例可以在python内置环境里运行,一些test client的功能,尤其是模板相关的功能,只有在test运行时才可以使用.


    Making requests

    可以使用django.test.client.Client类来构造request.它在构造时并不需要参数.
    >>> c = Client()

    一个client实例可以调用下列方法:

    get(path, data={}):
    创建一个path的Get请求,并返回Response object.
    Data字典的key-value值会被用来创建一个由Get得到的数据.如:
    c = Client()
    c.get('/customers/details/', {'name':'fred', 'age':7})
    它与下述请求等价:
    http://yoursite.com/customers/details/?name=fred&age=7

    post(path, data={}, content_type=MULTIPART_CONTENT)
    创建一个path的Get请求,并返回Response object.
    Data字典的key-value值会被用来创建POST的数据.如:
    >>> c = Client()
    >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
    它会请求'/login/'这个url,并发送数据name=fred&passwd=secret
    如果提供了content_type参数,data会被包含在POST请求里,以HTTP头Content-Type格式被发送.
    如果没有提供,data会以multipart/form-data的content_type格式被发送.这样的话,Data字典的key-value值会作为multipart message进行编码,并创建一个post数据载体.

    为一个参数提交多个多个值时--比如选住<select multiple>域的多个值--这些值可以是列表或者元组.举例来说,提交choice域的三个被选中的值:
    {'choices': ('a', 'b', 'd')}
    提交文件是一个特殊情况.你需要提供你想要上传的文件的文件名和操作这个文件的File变量:
    >>> c = Client()
    >>> f = open('wishlist.doc')
    >>> c.post('/customers/wishes/', {'name': 'fred', 'attachment': f})
    >>> f.close()
    注意:在你使用这个file后需要手动关闭它.

    login(**credentials)
    如果你的网站使用了django的验证系统,你可以使用test client的login()方法去登录用户.
    在你调用这个方法后,test client就会拥有登录用户所有的cookies 和 session.
    credentials参数的格式取决于你是用的验证程序(authentication backend,有setting里的AUTHENTICATION_BACKENDS定义).如果你使用的是django默认提供的验证程序就应该是用户名和密码.
    >>> c = Client()
    >>> c.login(username='fred', password='secret')
    >>> # Now you can access a view that's only available to logged-in users.
    如果你使用了不同的验证程序,login就需要不同的credentials参数,它需要的值由你定义的authenticate()决定.
    如果credentials通过验证,login()返回True,并登录成功.


    最后,记住在使用login()方法前,你需要创建一个账户,因为我们创建的测试数据库默认是没有任何数据的.你可以使用Django model api去创建一个用户账号,或者使用test fixture.

    logout()
    如果你的网站使用了django的验证系统,你可以使用test client的logout()方法注销用户.
    在你调用这个方法后,test client就会清理所有的cookie和session数据.

    测试responses
    get()和post()方法返回的都是Response object.这个Response object不同于view函数返回的HttpResponse.这个test Response object包含了更多的需要测试验证的数据.
    具体说,一个Response拥有下列的属性.
    属性 解释
    client 被用来生成request的TestClient
    content response的主体,string类型,是view render后的页面的最终内容,或者是错误信息.
    context 用来渲染模板的template Context.如果页面使用了多个模板,那context就会是Context Object列表.它们的排序方式就是它们被渲染的顺序.
    headers response的HTTP头,dict类型.
    request 作用于response的request数据
    status_code response的状态码,integer数据.
    template 被用来渲染最终的content的Template实例.template.name可以得到template的文件名,如果template是由文件载入的话(如 'admin/index.html').如果页面使用了多个模板,
    那template就会是Template列表,它们的排序方式就是它们被渲染的顺序.

    Exceptions
    如果你将TestClient指向了由view函数raise的异常,那这个异常在test case里是可见的.你可以使用标准的try...catch块或者unittest.TestCase.assertRaises()来测试它们.
    对testclient唯一不可见的异常是Http404,PermissionDenied和SystemExit.django会在内部捕捉这些异常并返回合适的response.这种情况下,你可以查看下你的response.status_code.

    Persistent state
    如果一个response返回了一个cookie,那么这个cookie就会被存储在test client里,并被其后的所有get()和post()传送.如果你想要终止这个cookie,你可以新建一个Client实例,或者手动删除它.(删除更有效)

    cookies python SimpleCookie object,包含了所有client cookies的当前值.
    session 一个类似于dict的object,包含了sesion信息

    例子
    import unittest
    from django.test.client import Client

    class SimpleTest(unittest.TestCase):
    def setUp(self):
    # Every test needs a client.
    self.client = Client()

    def test_details(self):
    # Issue a GET request.
    response = self.client.get('/customer/details/')

    # Check that the respose is 200 OK.
    self.failUnlessEqual(response.status_code, 200)

    # Check that the rendered context contains 5 customers.
    self.failUnlessEqual(len(response.context['customers']), 5)

    TestCase
    一般的python单元测试类要继承unittest.TestCase.django提供了一个子类--django.test.TestCase,提供了一些测试网站更有用的功能.
    将一个普通的unittest.TestCase转换为django TestCase非常容易:仅仅需要将base class 由unittest.TestCase换成django.test.TestCase.所有的标准python单元测试会继续工作.

    默认的 test client
    每一个test case都可以都可以访问test client.你也可以通过self.client访问它.

    这就意为着你不用在每个test方法中都实例化Client.
    import unittest
    from django.test.client import Client

    class SimpleTest(unittest.TestCase):
    def test_details(self):
    client = Client()
    response = client.get('/customer/details/')
    self.failUnlessEqual(response.status_code, 200)

    def test_index(self):
    client = Client()
    response = client.get('/customer/index/')
    self.failUnlessEqual(response.status_code, 200)

    用self.client来替换,就是
    from django.test import TestCase

    class SimpleTest(TestCase):
    def test_details(self):
    response = self.client.get('/customer/details/')
    self.failUnlessEqual(response.status_code, 200)

    def test_index(self):
    response = self.client.get('/customer/index/')
    self.failUnlessEqual(response.status_code, 200)

    Fixture loading
    如果数据库里没有数据,那么对于一个基于数据库的网站来说,test case并无多大的用处.为了给测试数据库加入测试数据更方便,django提供了载入fixtures的方法.
    fixture是一系列的数据,django知道如何将它导入数据库.
    创建fixture最直接的方法就是使用manage.py dumpdata.当然,这假设你的实际数据库里已经有数据了.

    注意:
    如果你运行过manage.py syncdb命令,那么你已经使用过fixture了--只是你不知道而已:)当你使用syncdb去创建数据库时,Fixture--initial_data.
    其他名字的Fixture可以通过manage.py loaddata命令手动安装.

    一旦建立了一个fixture,并将它放在了django project文件内,你就可以在你的测试类里使用它了.]
    from django.test import TestCase
    from myapp.models import Animal

    class AnimalTestCase(TestCase):
    fixtures = ['mammals.json', 'birds']

    def setUp(self):
    # Test definitions as before.

    def testFluffyAnimals(self):
    # A test that uses the fixtures.

    这是具体发生的过程:
    在setup()运行前,django会清空数据库,相当于你执行了syncdb.
    然后,所有的fixture会被安装.在例子中,django会安装任何一个名字为mammals的JSON格式的fixture和名为birds的fixture数据.

    这种清空/载入行为对test case里的每一个test都会重复执行一次
    来自:http://hi.baidu.com/apple_2005168/blog/item/91851b95c5ee6b4ed0135e0d.html
  • 相关阅读:
    【BZOJ4008】[HNOI2015] 亚瑟王(DP)
    【BZOJ4416】 [SHOI2013] 阶乘字符串(状压DP)
    【BZOJ4524】[CQOI2016] 伪光滑数(堆的套路题)
    【洛谷5336】[THUSC2016] 成绩单(区间DP)
    【洛谷4238】【模板】多项式乘法逆
    【洛谷4707】重返现世(kth Min-Max容斥+动态规划)
    【洛谷5339】[TJOI2019] 唱、跳、rap和篮球(容斥+NTT)
    【洛谷3723】[AH2017/HNOI2017] 礼物(FFT)
    【LOJ2290】「THUWC2017」随机二分图(状压+记忆化搜索)
    【洛谷5795】[THUSC2015] 异或运算(可持久化Trie)
  • 原文地址:https://www.cnblogs.com/chenjianhong/p/4144855.html
Copyright © 2011-2022 走看看