Django中自帶了一個單元測試類--django.test.TestCase,其是unittest.TestCase的子類,並標準的unittest進行了擴展,增加了不少測試相關的功能。用法與標準的unittest幾乎相同。
from django.test import TestCase # Create your tests here. from django.contrib.auth.models import User class UserTestCase(TestCase): def setUp(self): User.objects.create_user(username='duizhang',email="[email protected]",password="123456") def testUser(self): '''test''' u = User.objects.get(username='duizhang') self.assertEqual(u.username,'duizhang') self.assertEqual(u.email,'[email protected]') def testTrue(self): '''test true''' self.assertTrue('')
我這裏偷了個懶,直接使用的是Django自帶的user model進行測試。
執行測試用例:
python manage.py test
該命令會在當前目錄及子目錄搜索有所有的匹配到`test*.py`的文件,並執行裏面的測試用例。
也可以具體指定要執行的哪個應用下或者是哪個模塊的測試用例:
# Run all the tests in the animals.tests module $ ./manage.py test accounts.tests # Run all the tests found within the 'animals' package $ ./manage.py test accounts # Run just one test case $ ./manage.py test accounts.tests.UserTestCase # Run just one test method $ ./manage.py test animals.tests.UserTestCase.testUser
示例,只測試某個測試用例下的某一個測試功能:
$ python manage.py test accounts.tests.UserTestCase.testUser Creating test database for alias 'default'... .#創建一個測試用的數據庫 ---------------------------------------------------------------------- Ran 1 test in 0.034s#執行了幾個功能測試 OK Destroying test database for alias 'default'...#銷燬測試用的數據庫
也可以是指定值執行某個目錄下是測試:
$ ./manage.py test accounts/
使用正則表達式匹配要執行的測試文件:
$ ./manage.py test --pattern="tests_*.py"
在測試過程中會經常要往數據庫裏寫入數據的,如:測試model,在django中並不會使用你的“真實”的數據庫,而是再單獨創建一個數據庫用作測試。
無論測試是否通過,在所有的測試執行完之後,所使用的測試數據庫都會被刪除。
在Django1.8中,執行測試的命令中指定`--keepdb`,就不會刪除測試數據庫。
測試輸出的結果:
$ python manage.py test accounts.tests.UserTestCase Creating test database for alias 'default'...#創建測試用數據庫 F. ====================================================================== FAIL: testTrue (accounts.tests.UserTestCase)#測試失敗的用例 test true ---------------------------------------------------------------------- Traceback (most recent call last): File "d:\www\djtest\accounts\tests.py", line 24, in testTrue self.assertTrue('') AssertionError: '' is not true#測試是被的具體錯誤信息,及失敗出現在哪一行 ---------------------------------------------------------------------- Ran 2 tests in 0.069s#測試執行的時間 FAILED (failures=1)#有一個測試執行失敗 Destroying test database for alias 'default'...#銷燬測試用數據庫
以上的測試功能,其實都是標準的unittest的功能,除此以外Django還擴展了一些供測試用的小工具。
The test client
測試客戶端是一個python類,模擬一個web瀏覽器。可以用來測試視圖部分和Django應用程序的交互行爲。但是測試客戶端並不能替代selenium和一些瀏覽器框架, 使用Django測試客戶端建立一個模板渲染的鏈接,檢測模板是否被正確渲染,是否將數據正確傳遞給模板;使用類似selenium這樣的瀏覽器框架來測試HTML及相關的頁面行爲,如JS的功能,Django也提供了相關的功能--LiveServerTestCase。
我這裏以login視圖來測試,視圖代碼爲:
def login(request): '''login view''' username = request.POST['username'] password = request.POST['password'] return HttpResponse('usernames:'+username+',passwordssss:'+password)
在交互環境下執行測試:
$ python manage.py shell Python 2.7.10 (default, May 23 2015, 09:44:00) [MSC v.1500 64 bit (AMD64)] on w n32 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> from django.test import Client >>> c = Client() >>> re = c.post('/accounts/login',{'username':'duizhang','password':'123456'}) >>> re.status_code 200 >>> re.content 'usernames:duizhang,passwordssss:123456'
測試客戶端並不需要web服務的運行(即不需要提前執行python manage.py runserver ....),沒有任何web服務運行,測試客戶端也可以工作得很好。這樣就避免了HTTP協議的開銷,直接使用Django框架。這可以使單元測試更快速高效。 要檢索一個頁面的時候,注意URL格式,並不是一個完整的域名,要像下邊這樣指定:
>>> c.get('/login/')
並不能使用測試客戶端檢索非Django創建的頁面,如果需要檢索其他頁面可以使用Python的標準庫模塊`urllib`。
具體說下測試客戶端創建的get()和post()請求:
首先要實例化Client,實例化的時候也可以爲每個請求指定User-Agent:
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
此時就可以調用一些請求方法了。get()的格式:
get(path, data=None, follow=False, secure=False, **extra)
path是要請求的URL,data部分要指定傳遞給GET請求的參數。follow指定是否跟隨URL跳轉,secure表示是否啓動https,**extra可以指定一些額外的參數。
>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
上邊的例子就相當於請求了這個鏈接:
/customers/details/?name=fred&age=7
在`extra`上指定的關鍵字參數可以被用來指定一個特定的請求頭信息,這裏指定的參數會覆蓋在實例化Client時候構造函數默認傳遞的參數,如:
>>> c = Client() >>> c.get('/customers/details/', {'name': 'fred', 'age': 7},HTTP_X_REQUESTED_WITH='XMLHttpRequest')
將follow設置爲True,如有一個連接/redirect_me/會跳轉到/next/,然後再跳轉到/final/:
>>> response = c.get('/redirect_me/', follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]
post()方法的格式:
post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra)
在給定的path上創建一個post請求,並返回一個響應對象:
>>> from django.test import Client >>> c = Client() >>> re = c.post('/accounts/login',{'username':'duizhang','password':'123456'}) >>> re.status_code 200 >>> re.content 'usernames:duizhang,passwordssss:123456'
如果指定了`content_type`值(如:`text/xml`),會將HTTP頭信息中的`Content_type`指定爲該值。
如果沒有指定`content_type`,數據將會以`multipart/form-data`形式傳輸。在這種情況下,數據會被編碼爲一個multipart形式的消息作爲POST數據傳輸。
有時候也需要給一個鍵提交多個值,如`<select multiple>`標籤的值,這會提交一個列表或者一個元祖。如:`choices`字段提交多個值:
{'choices': ('a', 'b', 'd')}
要上傳一個文件,需要提供字段名做爲鍵,要上傳文件的句柄作爲值即可:
>>> c = Client() >>> with open('wishlist.doc') as fp: c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp}
如果要上傳的文件包含二進制數據的話,如上傳圖片,在要以`rb`格式打開文件。
`get()`和`post()`方法會返回一個`Response`對象,這個`Response`對象不同於Django視圖返回`HttpResonse`對象。測試response對象會有一個額外的數據(如返回碼等)用來做測試用。
Reponse的屬性:
`client` 發送請求的客戶端
`content` 響應體,以字符串返回。這是頁面的最終內容,或者是一個錯誤信息。
>>> re.content 'usernames:duizhang,passwordssss:123456'
`context` 模板的上下文實例,用於渲染響應內容的模板。
如果頁面使用了多個模板,`context`會是一個上下文實例的列表`request` 發送的請求數據
>>> re.request {u'CONTENT_LENGTH': 176, u'CONTENT_TYPE': 'multipart/form-data; boundary=BoUnDaR yStRiNg', u'wsgi.url_scheme': 'http', u'wsgi.input': <django.test.client.FakePay load object at 0x0000000003F71B00>, u'REQUEST_METHOD': 'POST', u'PATH_INFO': '/a ccounts/login', u'SERVER_PORT': '80', u'QUERY_STRING': ''}
`wsgi_request` `WSGIRequest`實例。
`status_code` HTTP返回狀態碼
>>> re.status_code
200`templates` 所渲染的模板實例的列表。
TestCase還提供了一個setUpTestData()方法, 在類級別爲整個`TestCase`一次性初始化數據,與原生的單元測試庫`setUp`相比,性能較好。
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...
注:如果測試用的數據庫是不支持事務的(如MySQL的MyISAM引擎),此時`setUpTestData`會在每次test之前沒調用,此時與原生的單元測試相比並沒有性能提升。
總體來說測試客戶端爲測試視圖請求提供了一個很方便的渠道,如測試調轉是否正確,測試鏈接是否正確get,測試視圖返回的數據是否符合期望等等。
剛纔也說了,測試客戶端並不能代替selenium來模擬瀏覽器動作,測試一些頁面上的功能,但是Django也提供了相關功能---LiveServerTestCase,使用起來也是比較方便的,同樣的直接貼出官方文檔的示例代碼:
from django.test import LiveServerTestCase from selenium.webdriver.firefox.webdriver import WebDriver class MySeleniumTests(LiveServerTestCase): fixtures = ['user-data.json'] @classmethod def setUpClass(cls): super(MySeleniumTests,cls).setUpClass() cls.selenium = WebDriver() @classmethod def tearDownClass(cls): cls.selenium.quit() super(MySeleniumTests,cls).tearDownClass() def test_login(self): self.selenium.get('%s%s' % (self.live_server_url, '/login/')) username_input = self.selenium.find_element_by_id("login-username") username_input.send_keys('admin') password_input = self.selenium.find_element_by_id("login-password") password_input.send_keys('123456') self.selenium.find_element_by_xpath('//input[@value="userLogin"]').click()
注:在使用之前要先安裝python的selenium庫,easy_install selenium。