Django 1.6 最佳实践: 如何正确进行 Unit Tests

作者: Desmond Chen, 发布日期: 2014-06-11, 修改日期: 2014-06-11

为什么我们要写Unit test? 因为随着代码量的增加, 开发人员会忘记, 因此需要Unit test帮助我们保证程序的可靠性. 尤其是我们的程序涉及到医疗信息, 关乎生命安全, 或关乎其他人的资金.

1. test文件准备

当我们使用manage.py startapp创建新的Django app时, django会为我们创建test.py. 我们需要做的第一步是删除该文件, 然后建立test_models.py, test_forms.py, test_views.py文件:

    myapp/
        __init__.py
        admin.py
        forms.py
        models.py
        test_forms.py
        test_models.py
        test_views.py
        views.py

如果项目中出现其他代码文件, 则建立相应的test文件. 这样做的原因是使测试文件扁平化, 方便我们更容易浏览和修改. 注意, 则是文件必须以"test_"开头, 否则django无法发现这些测试文件.

2. 如何写 Unit Tests

a. 每个method只测试一项

每个test method应当尽量减少其测试的范围, 不要尝试在一个method中测试多个views, models, forms.

当然, 这里也会出现难题, 因为通常一个view会涉及到models, forms, 其他methods和functions. 此时我们就最简化我们的环境:

    # 测试 REST Api
    # test_api.py
    import json

    from django.core.urlresolvers import reverse
    from django.test import TestCase

    from myapp.models import Article

    class ArticleTests(TestCase):

        def setUp(self):
            Article.objects.get_or_create(title="A title", slug="a-slug")

        def test_list(self):
            url = reverse("article_object_api")
            response = self.client.get(url)
            self.assertEquals(response.status_code, 200)
            data = json.loads(response.content)
            self.assertEquals(len(data), 1)

以上代码中, 我们使用setUp method, 最简化了我们需要的model. 以下是一个更为完整的例子, 测试使用django_rest_framework构建的REST API:

    # test_api.py
    import json

    from django.core.urlresolvers import reverse
    from django.test import TestCase
    from django.utils.http import urlencode

    from myapp.models import Article

    class ArticleTests(TestCase):

        def setUp(self):
            Article.objects.get_or_create(title="title1", slug="slug1")
            Article.objects.get_or_create(title="title2", slug="slug2")

            self.create_read_url = reverse("article_rest_api")
            self.read_update_delete_url = reverse("article_rest_api", kwargs={"slug": "slug1"})

        def test_list(self):
            response = self.client.get(self.create_read_url)

            # content中, 两个title是否都有?
            self.assertContains(response, "title1")
            self.assertContains(response, "title2")

        def test_detail(self):
            response = self.client.get(self.read_update_delete_url)
            data = json.loads(response.content)
            content = {"id": 1, "title": "title1", "slug": "slug1"}

            self.assertEquals(data, content)

        def test_create(self):
            post = {"title": "title3", "slug": "slug3"}
            response = self.client.post(self.create_read_url, post)
            data = json.loads(response.content)
            self.assertEquals(response.status_code, 201)
            content = {"id": "3", "title": "title3", "slug": "slug3"}

            self.assertEquals(data, content)
            self.assertEquals(Article.objects.count(), 3)

        def test_delete(self):
            response = self.client.delete(self.read_update_delete_url)

            self.assertEquals(response.status_code, 204)
            self.assertEquals(Article.objects.count(), 1)
b. 使用Request Factory测试view

django.test.client.RequestFactory可以生成用于view的request. 这样为我们提供了很大的便利性来定义request, 并能与标准test client隔离. 但是需要注意的是, request factory不支持middleware, 包括session和authentication.

c. 保持简单

tests应当尽可能的保持简单. 如果一个tesst中的code过于复杂, 那么我们可能需要为这一test再写一个test, 可见在这会在debug时造成多大的痛苦.

d. "不重复"原则不适用于 tests

有时我们需要相似但不同的数据来运行每个test method, 我们可能会尝试将他们写到一起, 并通过修改几个参数来达到test的目的. 但请不要这么做, 最好的方式其实是copy/paste这些代码, 使其扁平化.

e. 不要依赖fixtures

开发过程中, 数据结构的改变导致fixtures难以维护, 之前保存的fixture可能已经不再适用于现在的test. 因此尽量使用django自带的ORM来取代fixture.

也有其他开发人员喜欢使用以下这些工具:

  • factory boy: 用于生成model数据
  • model mommy: 用于生成model 数据
  • mock: 不只是用于django, 已被纳入Python 3.3 标准库
f. 测试覆盖范围

以下代码都应当被测试:

  • Views: 数据的改变, 自定义的method
  • Models: model的创建/更新/删除, model的method, model manager的method
  • Forms: form method, clean() method, 自定义field
  • Validators
  • Signals
  • Filters
  • Template Tag
  • 其他: context processors, middleware, email等
g. 文档

对于test class和method, 都应当说明测试目的.

使用coverage.py

coverage.py为我们提供了哪些部分被测试了, 哪些部分未被测试的详细信息.


原文链接: http://www.weiguda.com/blog/31/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值