Django中的單元測試

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。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章