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/


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com