Django 1.6 的測試驅動開發

http://www.oschina.net/translate/django-1-6-test-driven-developmentcss

測試驅動開發(TDD)是一個迭代的開發週期,強調編寫實際代碼以前編寫自動化測試。html

這個過程很簡單:python

  1. 先編寫測試。jquery

  2. 查看測試失敗的地方git

  3. 編寫足夠的代碼以使測試經過。github

  4. 再次測試。web

  5. 代碼重構 。sql

  6. 重複以上操做。數據庫

tdd-process

目錄django

這是一個長的帖子,爲了您的方便因此提供了目錄:

  1. 什麼是TDD

  2. 第一次測試

  3. 安裝Django

  4. 功能測試

  • 管理員登陸

  • 設置聯繫人應用程序

    5.單位測試

  • 主界面

  • 全部聯繫人視圖

  • 添加聯繫人視圖

  • 視圖

  • 驗證

  • 建立聯繫人

    6.功能測試完回家

    7.結構測試

    8,總結

一我的的北京
一我的的北京
翻譯於 4個月前

1人頂

 

 翻譯的不錯哦!

爲何要用TDD?

使用TDD,你將學會把你的代碼拆分紅符合邏輯的,簡單易懂的片斷,這有助於確保代碼的正確性。

這一點很是重要,由於作到下面這些事情是很是困難的:

  1. 在咱們的腦中一次性處理全部複雜的問題。

  2. 瞭解什麼時候從哪裏開始着手解決問題。

  3. 在代碼庫的複雜度不斷增加的同時不引入錯誤和bug;而且

  4. 辨別出代碼在何時發生了問題。

TDD幫助咱們定位問題。它不能保證你的代碼徹底沒有錯誤;然而,你能夠寫出更好的代碼,從而能更好地理解理解代碼。這自己有助於消除錯誤,而且至少,你能夠更容易的定位錯誤。

TTD實際上也是一種行業標準。

說的夠多了。讓咱們來看看代碼吧。

在這個教程裏,咱們將建立一個存儲用戶聯繫人的app。

請注意: 這篇教程假設你運行在一個基於Unix的環境裏 - 例如, Mac OSX, Linux, 或者在Windows下的Linux VM。 我將使用Sublime 2做爲文本編輯器。而且,確保你已經完成了官方的Django教程而且基本瞭解Python語言. 此外,在這個第一篇post裏,咱們不會涉及到Django1.6提供的新工具。這篇文章將爲以後的post打好基礎來處理不一樣形式的測試。

BoydWang
BoydWang
翻譯於 4個月前

0人頂

 

 翻譯的不錯哦!

第一個測試

在開始作一些事情以前,咱們須要首先建立一個測試。爲了這個測試,咱們須要讓Django正確安裝。爲此咱們將使用一個函數測試——這在下面會詳細解釋。

  1. 建立一個新目錄存放你的項目:

    1 $ mkdir django-tdd
    2 $ cd django-tdd

     

  2. 再創建一個目錄存放函數測試

    1 $ mkdir ft
    2 $ cd ft

     

  3. 建立一個新文件 "tests.py"並加入如下代碼:

    1 from selenium import webdriver
    2  
    3 browser = webdriver.Firefox()browser.get('http://localhost:8000/')body = browser.find_element_by_tag_name('body')assert 'Django' in body.text
    4  
    5 browser.quit()

     

  4. 如今運行測試:

    1 $ python tests.py

     

    確認安裝selenium(譯註:自動化測試軟件)時是使用 installed -pip安裝的

    你將看到 FireFox彈出來試圖打開 http://localhost:8000/。在你的終端上面你會看到:

    1 Traceback (most recent call last):File "tests.py", line 7in <module>assert 'Django' in body.textAssertionError

     

    祝賀!你完成了第一個失效測試。

    如今咱們寫足夠的代碼來讓它經過,這些代碼量約至關於設置一個 Django 開發環境。

super0555
super0555
翻譯於 4個月前

0人頂

 

 翻譯的不錯哦!

設置Django

1. 激活一個virtualenv:

1 $ cd ..
2 $ virtualenv --no-site-packages env
3 $ source env/bin/activate

2. 安裝Django而且創建一個項目

1 $ pip install django==1.6.1$ django-admin.py startproject contacts

你當前的項目結構應該是下面這個樣子:

1 ├── contacts
2 │   ├── contacts
3 │   │   ├── __init__.py
4 │   │   ├── settings.py
5 │   │   ├── urls.py
6 │   │   └── wsgi.py
7 │   └── manage.py
8 └── ft    
9     └── tests.py

3. 安裝 Selenium:

1 pip install selenium==2.39.0

4. 運行server

1 $ cd contacts
2 $ python manage.py runserver

5. 接着,打開一個新終端窗口,定位到"ft"文件夾下,再運行一次測試:

1 $ python tests.py

你將看到FireFox又一次窗口導航到了http://localhost:8000/。此次應該沒有錯誤了。你剛剛已經經過了你的第一個測試!如今,讓咱們完成環境設置。

6. 版本控制,首先添加一個".gitignore"而且在裏面添加下面的代碼:

1 .Pythonenv
2 bin
3 lib
4 include.DS_Store.pyc

如今來建立一個Git倉庫而後提交吧

1 $ git init
2 $ git add .$ git commit -am "initial"

7. 項目建完了,如今咱們回頭討論一下功能測試吧。 

BoydWang
BoydWang
翻譯於 4個月前

0人頂

 

 翻譯的不錯哦!

功能測試

咱們經過用 Selenium 來進行第一次測試。這樣的測試會使咱們使用web瀏覽器就像咱們是最終用戶同樣,來看看應用程序其實是怎麼運行的。由於這些測試是遵循最終用戶的行爲習慣——也能夠說是用戶用例——這個包含了對一系列產品特色進行測試,而不只僅對單一功能進行測試——這種更適合單元測試。有一點很是須要注意的是,當這部分測試代碼你還沒開始寫,那麼你必須先從功能測試開始。因爲咱們基本上是測試Django的代碼,因此功能測試是一個正確的方法去作的。

另外一種方式去思考功能測試和單元測試的區別,就是功能測試主要關注在應用程序的外部,從用戶的角度來進行測試,而單元測試主要是關注在應用程序的內部,從開發的角度進行測試。

在實踐中會更多地體現這個概念。

在繼續下個話題以前,咱們先來重構咱們的測試環境,使得測試起來更加簡單。

  1. 首先,咱們要重寫在「tests.py」文件內的第一個測試:

    01 from selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom django.test import LiveServerTestCaseclass AdminTest(LiveServerTestCase):
    02  
    03   def setUp(self):
    04       self.browser = webdriver.Firefox()
    05  
    06   def tearDown(self):
    07       self.browser.quit()
    08  
    09   def test_admin_site(self):    
    10       # user opens web browser, navigates to admin page
    11       self.browser.get(self.live_server_url + '/admin/')
    12       body = self.browser.find_element_by_tag_name('body')
    13       self.assertIn('Django administration', body.text)

     

  2. 而後運行它:

    1 $ python manage.py test ft

     

    它會經過:

    1 ----------------------------------------------------------------------Ran 1 test in 3.304sOK

     

    恭喜你!

    在繼續以前,咱們先看看這裏是怎麼回事。若是全部都經過了,你也會看到FireFox瀏覽器被打開,而後按照咱們在測試裏所用的setUp()和tearDown()方法設置的功能進行整個過程。這個測試自己只是簡單的測試這個"/admin" (self.browser.get(self.live_server_url + '/admin/')頁面是否被找到,"Django administration"這個單詞是否出如今body標籤內。

    讓咱們確認一下。

  3. 運行服務:

    1 $ python manage.py runserver

     

    在地址欄裏敲上地址 http://localhost:8000/admin/ 你會看到:

    admin-page

  4. 咱們能夠只需對錯誤的東西進行簡單地測試便能確認測試是否正確運做。更新測試裏的最後一行:

    1 self.assertIn('administration Django', body.text)

     

    從新再運行一次。你會發現有如下的錯誤(固然是咱們所指望的):

    1 AssertionError: 'administration Django' not found in u'Django administration\nUsername:\nPassword:\n '

     

    修正測試,再測試一遍,就能夠提交代碼了。

    最後,你有沒有注意到,咱們用來進行實際測試的功能名稱均以test_開頭。這是爲了讓Django測試運行器能找到這些測試。換句話來講,任何一個以test_開頭命名的功能都會被測試運行器視爲一個測試。

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

管理員登錄

接下來,讓咱們來測試,以確保用戶能夠登陸到管理網站。

  1. 更新「tests.py」文件中的test_admin_site功能:

    01 def test_admin_site(self):    
    02   # user opens web browser, navigates to admin page
    03   self.browser.get(self.live_server_url + '/admin/')
    04   body = self.browser.find_element_by_tag_name('body')
    05   self.assertIn('Django administration', body.text)
    06   # users types in username and passwords and presses enter
    07   username_field = self.browser.find_element_by_name('username')
    08   username_field.send_keys('admin')
    09   password_field = self.browser.find_element_by_name('password')
    10   password_field.send_keys('admin')
    11   password_field.send_keys(Keys.RETURN)
    12   # login credentials are correct, and the user is redirected to the main admin page
    13   body = self.browser.find_element_by_tag_name('body')
    14   self.assertIn('Site administration', body.text)

     

    因此 -

  • find_element_by_name- 是用於定位輸入框。

  • send_keys- 發送鍵盤按鍵信息。

  1. 運行測試,你會發現這個錯誤:

    1 AssertionError: 'Site administration' not found in u'Django administration\nPlease enter the correct username and password for a staff account. Note that both fields may be case-sensitive.\nUsername:\nPassword:\n '

     

    這個之因此會失敗,是由於咱們沒有管理員用戶設置。這是一個預期中的失敗,因此出現這種狀況是對的。換句話來講,咱們知道它會失敗的,這使得咱們更容易去解決它。

  2. 同步數據庫:

    1 $ python manage.py syncdb

     

    設置一個管理員用戶。

    再從新測試一遍。它依舊會失敗。爲何呢?由於Django在運行的時候會給咱們數據庫建立一份副本,這樣的測試方式不會影響生產數據庫。

  3. 咱們須要設置一個Fixture,是一個包含了咱們想加載到測試數據庫的數據文件:登陸憑據。爲了要實現這一點,當運行如下命令時,可以將數據庫管理員用戶信息從數據庫轉存到Fixture中去:

    1 $ mkdir ft/fixtures
    2 $ python manage.py dumpdata auth.User --indent=2 > ft/fixtures/admin.json

     

    如今更新AdminTest類:

    01 class AdminTest(LiveServerTestCase):
    02  
    03     # load fixtures
    04   fixtures = ['admin.json']
    05  
    06   def setUp(self):
    07       self.browser = webdriver.Firefox()
    08  
    09   def tearDown(self):
    10       self.browser.quit()
    11  
    12   def test_admin_site(self):    
    13       # user opens web browser, navigates to admin page
    14       self.browser.get(self.live_server_url + '/admin/')
    15       body = self.browser.find_element_by_tag_name('body')
    16       self.assertIn('Django administration', body.text)
    17       # users types in username and passwords and presses enter
    18       username_field = self.browser.find_element_by_name('username')
    19       username_field.send_keys('admin')
    20       password_field = self.browser.find_element_by_name('password')
    21       password_field.send_keys('admin')
    22       password_field.send_keys(Keys.RETURN)
    23       # login credentials are correct, and the user is redirected to the main admin page
    24       body = self.browser.find_element_by_tag_name('body')
    25       self.assertIn('Site administration', body.text)

     

    運行這個測試,它會經過。

    每次運行測試的時候,Django都會轉存測試數據庫。而這全部的Fixture都會在「test.py」文件中被指定加載到數據庫中去。

  4. 讓咱們加一個或多個斷言。再次更新測試:

    01 def test_admin_site(self):    
    02     # user opens web browser, navigates to admin page
    03     self.browser.get(self.live_server_url + '/admin/')
    04     body = self.browser.find_element_by_tag_name('body')
    05     self.assertIn('Django administration', body.text)
    06     # users types in username and passwords and presses enter
    07     username_field = self.browser.find_element_by_name('username')
    08     username_field.send_keys('admin')
    09     password_field = self.browser.find_element_by_name('password')
    10     password_field.send_keys('admin')
    11     password_field.send_keys(Keys.RETURN)
    12     # login credentials are correct, and the user is redirected to the main admin page
    13     body = self.browser.find_element_by_tag_name('body')
    14     self.assertIn('Site administration', body.text)
    15     # user clicks on the Users link
    16     user_link = self.browser.find_elements_by_link_text('Users')
    17     user_link[0].click()
    18     # user verifies that user live@forever.com is present
    19     body = self.browser.find_element_by_tag_name('body')
    20     self.assertIn('live@forever.com', body.text)

     

  5. 運行它,它會失敗,由於咱們須要添加另外一個用戶到fixture文件中:

    01 [{"pk"1"model""auth.user""fields": {
    02   "username""admin"
    03   "first_name": "", 
    04   "last_name": "", 
    05   "is_active": true, 
    06   "is_superuser": true, 
    07   "is_staff": true, 
    08   "last_login""2013-12-29T03:49:13.545Z"
    09   "groups": [], 
    10   "user_permissions": [], 
    11   "password""pbkdf2_sha256$12000$VtsgwjQ1BZ6u$zwnG+5E5cl8zOnghahArLHiMC6wGk06HXrlAijFFpSA="
    12   "email""ad@min.com"
    13   "date_joined""2013-12-29T03:49:13.545Z"}},{"pk"2"model""auth.user""fields": {
    14   "username""live"
    15   "first_name": "", 
    16   "last_name": "", 
    17   "is_active": true, 
    18   "is_superuser": false, 
    19   "is_staff": false, 
    20   "last_login""2013-12-29T03:49:13.545Z"
    21   "groups": [], 
    22   "user_permissions": [], 
    23   "password""pbkdf2_sha256$12000$VtsgwjQ1BZ6u$zwnG+5E5cl8zOnghahArLHiMC6wGk06HXrlAijFFpSA="
    24   "email""live@forever.com"
    25   "date_joined""2013-12-29T03:49:13.545Z"}}]

     

再次運行,它是會經過的。若是須要能夠重構一下這個測試。如今想一想還有什麼能夠測試。或許你能夠測試管理員用戶能夠添加一個用戶到管理面板中,或者能夠測試沒有管理員權限的人是不能進入管理面板中。寫幾個測試,更新你的代碼,再次測試,根據須要重構代碼。

接下來,咱們會添加增長聯繫人應用,不要忘了提交代碼哦!

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

設置聯繫人應用

  1. 開始一個測試,添加如下功能:

    01 def test_create_contact_admin(self):    
    02   self.browser.get(self.live_server_url + '/admin/')
    03   username_field = self.browser.find_element_by_name('username')
    04   username_field.send_keys('admin')
    05   password_field = self.browser.find_element_by_name('password')
    06   password_field.send_keys('admin')
    07   password_field.send_keys(Keys.RETURN)
    08   # user verifies that user_contacts is present
    09   body = self.browser.find_element_by_tag_name('body')
    10   self.assertIn('User_Contacts', body.text)
  2. 再次運行測試,你會看到如下錯誤:

    1 AssertionError: 'User_Contacts' not found in u'Django administration\nWelcome, admin. Change password / Log out\nSite administration\nAuth\nGroups\nAdd\nChange\nUsers\nAdd\nChange\nRecent Actions\nMy Actions\nNone available'

    這是預料之中的。 

    如今,咱們要寫足夠的代碼讓它經過。

  3. 新建一個應用:

    1 $ python manage.py startapp user_contacts
  4. 添加到「settings.py」文件:

    1 INSTALLED_APPS = (
    2   'django.contrib.admin',
    3   'django.contrib.auth',
    4   'django.contrib.contenttypes',
    5   'django.contrib.sessions',
    6   'django.contrib.messages',
    7   'django.contrib.staticfiles',
    8   'ft',
    9   'user_contacts',)
  5. 在user_contacts目錄下的「admin.py」文件中添加如下代碼:

    1 from user_contacts.models import Person, Phonefrom django.contrib import admin
    2  
    3 admin.site.register(Person)admin.site.register(Phone)
  6. 你的工程架構會跟以下相似:

    01 .├── user_contacts
    02  │   ├── __init__.py
    03  │   ├── admin.py   
    04  │   ├── models.py
    05  │   ├── tests.py
    06  │   └── views.py
    07  ├── contacts
    08  │   ├── __init__.py
    09  │   ├── settings.py
    10  │   ├── urls.py
    11  │   └── wsgi.py
    12  ├── ft
    13  │   ├── __init__.py
    14  │   ├── fixtures
    15  │   │   └── admin.json
    16  │   └── tests.py
    17  └── manage.py
  7. 更新「models.py」:

    01 from django.db import modelsclass Person(models.Model):
    02   first_name = models.CharField(max_length = 30)
    03   last_name = models.CharField(max_length = 30)
    04   email = models.EmailField(null = True, blank = True)
    05   address = models.TextField(null = True, blank = True)
    06   city = models.CharField(max_length = 15, null = True,blank = True)
    07   state = models.CharField(max_length = 15, null = True, blank = True)
    08   country = models.CharField(max_length = 15, null = True, blank = True)
    09  
    10   def __unicode__(self):
    11       return self.last_name +", "+ self.first_nameclass Phone(models.Model):
    12   person = models.ForeignKey('Person')
    13   number = models.CharField(max_length=10)
    14  
    15   def __unicode__(self):
    16       return self.number
  8. 再次運行測試,你會看到:

    1 Ran 2 tests in 11.730sOK
  9. 咱們繼續下一步驟,添加測試進去以保證管理員能夠添加數據:

    01 # user clicks on the Persons link
    02 persons_links = self.browser.find_elements_by_link_text('Persons')
    03 persons_links[0].click()
    04 # user clicks on the Add person link
    05 add_person_link = self.browser.find_element_by_link_text('Add person')
    06 add_person_link.click()
    07 # user fills out the form
    08 self.browser.find_element_by_name('first_name').send_keys("Michael")
    09 self.browser.find_element_by_name('last_name').send_keys("Herman")
    10 self.browser.find_element_by_name('email').send_keys("michael@realpython.com")
    11 self.browser.find_element_by_name('address').send_keys("2227 Lexington Ave")
    12 self.browser.find_element_by_name('city').send_keys("San Francisco")
    13 self.browser.find_element_by_name('state').send_keys("CA")
    14 self.browser.find_element_by_name('country').send_keys("United States")
    15 # user clicks the save button
    16 self.browser.find_element_by_css_selector("input[value='Save']").click()
    17 # the Person has been added
    18 body = self.browser.find_element_by_tag_name('body')
    19 self.assertIn('Herman, Michael', body.text)
    20 # user returns to the main admin screen
    21 home_link = self.browser.find_element_by_link_text('Home')
    22 home_link.click()
    23 # user clicks on the Phones link
    24 persons_links = self.browser.find_elements_by_link_text('Phones')
    25 persons_links[0].click()
    26 # user clicks on the Add phone link
    27 add_person_link = self.browser.find_element_by_link_text('Add phone')
    28 add_person_link.click()
    29 # user finds the person in the drop
    30 downel = self.browser.find_element_by_name("person")
    31 for option in el.find_elements_by_tag_name('option'):
    32   if option.text == 'Herman, Michael':
    33       option.click()
    34 # user adds the phone numbers
    35 self.browser.find_element_by_name('number').send_keys("4158888888")
    36 # user clicks the save button
    37 self.browser.find_element_by_css_selector("input[value='Save']").click()
    38 # the Phone has been added
    39 body = self.browser.find_element_by_tag_name('body')
    40 self.assertIn('4158888888', body.text)
    41 # user logs out
    42 self.browser.find_element_by_link_text('Log out').click()
    43 body = self.browser.find_element_by_tag_name('body')
    44 self.assertIn('Thanks for spending some quality time with the Web site today.', body.text)

這就是管理員的功能。讓咱們轉過頭來專一於user_contacts自己。你以前的代碼還記得提交嗎?若是沒有,趕忙提交吧!

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

單元測試

考慮下咱們如今已經寫的特性。咱們已經定義了咱們的模型,容許管理員更改模型。根據這個狀況和咱們項目的總體目標,着重關注剩下的用戶功能。

用戶應該能夠——

  1. 瀏覽全部的聯繫人。

  2. 添加新的聯繫人。

根據這些需求,嘗試把剩下的功能測試公式化。儘管,在咱們寫功能測試以前,咱們應該經過單元測試定義代碼的行爲——這有助於你寫出良好、乾淨的代碼,編寫功能測試更加簡單。

 

記住:功能測試最終將表示你的項目是否工做,而單元測試有助於你達到這樣的目的。這很快就會變的有意義。

讓咱們暫停片刻,談論一些常規慣例。

儘管TDD(或者終端)的基礎——測試、代碼、重構——是通用的,不少開發者使用的方法是不一樣的。例如,我喜歡先寫單元測試,保證咱們的代碼在細粒度級別有效,而後寫功能測試。其餘開發者先寫功能測試,查看它們失敗,而後寫單元測試,查看它們失敗,而後再寫代碼,首先知足單元測試,最終也應該知足功能測試。這裏沒有正確和錯誤的答案。哪一種方法舒服用哪一種——但繼續先測試、而後寫代碼,最後重構。

Ley
Ley
翻譯於 3個月前

0人頂

 

 翻譯的不錯哦!

視圖

首先,檢查全部視圖都設置準確。

主視圖

  1. 跟往常同樣,先開始一個測試:

    1 from django.template.loader import render_to_stringfrom django.test import TestCase, Clientfrom user_contacts.models import Person, Phonefrom user_contacts.views import *class ViewTest(TestCase):def setUp(self):
    2     self.client_stub = Client()def test_view_home_route(self):
    3     response = self.client_stub.get('/')
    4     self.assertEquals(response.status_code, 200)

     

  2. 給這個測試文件取名爲test_views.py,並保存到user_contacts/tests目錄下。同時要添加__init__.py文件到目錄中去,在user_contacts主目錄下刪除"tests.py"文件。

  3. 運行它:

    1 $ python manage.py test user_contacts

     

    它會失敗的 -AssertionError: 404 != 200- 由於URL、視圖和模板都還沒存在。若是你不熟悉Django如何處理MVC架構,請點擊這裏閱覽這篇簡短的文章。咱們首先獲取用客戶端獲取url的「/」地址,這事Django的TestCase的一部分。這個響應被存儲起來,而後咱們去檢查返回的狀態碼是否等於200。

  4. 添加以下路徑到「contacts/urls.py」:

    1 url(r'^', include('user_contacts.urls')),

     

  5. 更新「contacts/urls.py」:

    1 from django.conf.urls import patterns, urlfrom user_contacts.views import *urlpatterns = patterns('',
    2     url(r'^$', home),)

     

  6. 更新「views.py」:

    1 from django.http import HttpResponse, HttpResponseRedirectfrom django.shortcuts import render_to_response, renderfrom django.template import RequestContextfrom user_contacts.models import Phone, Person# from user_contacts.new_contact_form import ContactFormdef home(request):
    2   return render_to_response('index.html')

     

  7. 添加「index.html」模板到模板目錄中去:

    01 <!DOCTYPE html><html>
    02   <head>
    03     <title>Welcome.</title>
    04     <meta name="viewport" content="width=device-width, initial-scale=1.0">
    05     <link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen">
    06     <style>
    07         .container {
    08             padding: 50px;
    09         }
    10     </style>
    11   </head>
    12   <body>
    13     <div class="container">
    14         <h1>What would you like to do?</h1>
    15         <ul>
    16             <li><a href="/all">View Contacts</a></li>
    17             <li><a href="/add">Add Contact</a></li>
    18         </ul>
    19     <div>
    20     <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
    22   </body></html>

     

  8. 再次運行測試,它就會順利經過。

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

全部聯繫人視圖

對這個視圖的測試幾乎和咱們上一個測試相同。在看個人答案以前先本身試試吧。

1.經過在ViewTest類裏添加下面的方法來開始這個測試。

1 def test_view_contacts_route(self):
2   response = self.client_stub.get('/all/')
3   self.assertEquals(response.status_code, 200)

2. 在運行時,你將看到一樣的錯誤:AssertionError: 404 != 200 

3. 用下面的路由策略更新"user_contacts/urls.py":

1 url(r'^all/$', all_contacts),

4. 更新"view.py":

1 def all_contacts(request):
2   contacts = Phone.objects.all()
3   return render_to_response('all.html', {'contacts':contacts})

5. 在templates文件夾里加入一個叫"all.html"的模板:

01 <!DOCTYPE html><html><head><title>All Contacts.</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" media="screen"><style>
02   .container {
03     padding: 50px;
04   }</style></head><body><div class="container">
05   <h1>All Contacts</h1>
06   <table border="1" cellpadding="5">
07     <tr>
08       <th>First Name</th>
09       <th>Last Name</th>
10       <th>Address</th>
11       <th>City</th>
12       <th>State</th>
13       <th>Country</th>
14       <th>Phone Number</th>
15       <th>Email</th>
16     </tr>
17     {% for contact in contacts %}      <tr>
18         <td></td>
19         <td></td>
20         <td></td>
21         <td></td>
22         <td></td>
23         <td></td>
24         <td></td>
25         <td></td>
26       </tr>
27     {% endfor %}  </table>
28   <br>
29   <a href="/">Return Home</a></div><script src="http://code.jquery.com/jquery-1.10.2.min.js"></script><script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script></body></html>

6. 而後測試應該能經過了。

增長聯繫人視圖

這個測試與前面兩個稍有不一樣,因此必定要仔細的跟着下列步驟走。

1. 在test suite里加入測試:

1 def test_add_contact_route(self):
2   response = self.client_stub.get('/add/')
3   self.assertEqual(response.status_code, 200)

2. 你將在運行時看到這樣的錯誤:AssertionError: 404 != 200

3. 更新"urls.py":

1 url(r'^add/$', add),

4. 更新"views.py"

1 def add(request):person_form = ContactForm()return render(request, 'add.html', {'person_form' : person_form}, context_instance = RequestContext(request))

確保加入了以下的引用:

1 from user_contacts.new_contact_form import ContactForm

5. 建立一個叫 new_contact_form.py的新文件而後加入以下代碼:

01 import refrom django import formsfrom django.core.exceptions import ValidationErrorfrom user_contacts.models import Person, Phoneclass ContactForm(forms.Form):
02   first_name = forms.CharField(max_length=30)
03   last_name = forms.CharField(max_length=30)
04   email = forms.EmailField(required=False)
05   address = forms.CharField(widget=forms.Textarea, required=False)
06   city = forms.CharField(required=False)
07   state = forms.CharField(required=False)
08   country = forms.CharField(required=False)
09   number = forms.CharField(max_length=10)
10  
11   def save(self):
12       if self.is_valid():
13           data = self.cleaned_data
14           person = Person.objects.create(first_name=data.get('first_name'), last_name=data.get('last_name'),
15               email=data.get('email'), address=data.get('address'), city=data.get('city'), state=data.get('state'),
16               country=data.get('country'))
17           phone = Phone.objects.create(person=person, number=data.get('number'))
18           return phone

6. 加入"add.html"到模板文件夾裏:

01 import refrom django import formsfrom django.core.exceptions import ValidationErrorfrom user_contacts.models import Person, Phoneclass ContactForm(forms.Form):
02   first_name = forms.CharField(max_length=30)
03   last_name = forms.CharField(max_length=30)
04   email = forms.EmailField(required=False)
05   address = forms.CharField(widget=forms.Textarea, required=False)
06   city = forms.CharField(required=False)
07   state = forms.CharField(required=False)
08   country = forms.CharField(required=False)
09   number = forms.CharField(max_length=10)
10  
11   def save(self):
12       if self.is_valid():
13           data = self.cleaned_data
14           person = Person.objects.create(first_name=data.get('first_name'), last_name=data.get('last_name'),
15               email=data.get('email'), address=data.get('address'), city=data.get('city'), state=data.get('state'),
16               country=data.get('country'))
17           phone = Phone.objects.create(person=person, number=data.get('number'))
18           return phone

7. 是否是經過了?應該是的。若是沒有,再檢查一下。

BoydWang
BoydWang
翻譯於 3個月前

0人頂

 

 翻譯的不錯哦!

驗證

如今咱們已經完成了視圖的測試,讓咱們添加對錶單的驗證。但首先咱們要寫一個測試,驚喜吧!

  1. 在「tests」目錄下新增一個叫「test_validator.py」的文件並增長如下代碼:

    01 from django.core.exceptions import ValidationError
    02     from django.test import TestCase
    03     from user_contacts.validators import validate_number, validate_string    class ValidatorTest(TestCase):
    04         def test_string_is_invalid_if_contains_numbers_or_special_characters(self):
    05             with self.assertRaises(ValidationError):
    06                 validate_string('@test')
    07                 validate_string('tester#')
    08         def test_number_is_invalid_if_contains_any_character_except_digits(self):
    09             with self.assertRaises(ValidationError):
    10                 validate_number('123ABC')
    11                 validate_number('75431#')

     

  2. 在運行測試以前,你猜猜會有什麼狀況發生?提示:請密切注意代碼上面導入進來的包。你會有如下錯誤信息,由於咱們沒有「validators.py」文件:

    1 ImportError: cannot import name validate_string

     

    換言之,咱們測試所需的邏輯驗證文件還不存在。

  3. 在「user_contacts」目錄下新增一個叫「validators.py」的文件:

    1 import refrom django.core.exceptions import ValidationErrordef validate_string(string):
    2   if re.search('^[A-Za-z]+$', string) is None:
    3       raise ValidationError('Invalid')def validate_number(value):
    4   if re.search('^[0-9]+$', value) is None:
    5       raise ValidationError('Invalid')

     

  4. 再次運行測試。5個測試會經過的:

    1 Ran 5 tests in 0.019sOK

     

新增聯繫人

  1. 因爲咱們增長了驗證,咱們想測試一下在管理員區域這個驗證功能是能夠工做的,因此更新「test_views.py」:

    01 from django.template.loader import render_to_stringfrom django.test import TestCase, Clientfrom user_contacts.models import Person, Phonefrom user_contacts.views import *class ViewTest(TestCase):
    02   def setUp(self):
    03       self.client_stub = Client()
    04       self.person = Person(first_name = 'TestFirst',last_name = 'TestLast')
    05       self.person.save()
    06       self.phone = Phone(person = self.person,number = '7778889999')
    07       self.phone.save()
    08   def test_view_home_route(self):
    09       response = self.client_stub.get('/')
    10       self.assertEquals(response.status_code, 200)
    11   def test_view_contacts_route(self):
    12       response = self.client_stub.get('/all/')
    13       self.assertEquals(response.status_code, 200)
    14   def test_add_contact_route(self):
    15       response = self.client_stub.get('/add/')
    16       self.assertEqual(response.status_code, 200)
    17   def test_create_contact_successful_route(self):
    18       response = self.client_stub.post('/create',data = {'first_name' 'testFirst''last_name':'tester''email':'test@tester.com''address':'1234 nowhere''city':'far away''state':'CO''country':'USA''number':'987654321'})
    19       self.assertEqual(response.status_code, 302)
    20   def test_create_contact_unsuccessful_route(self):
    21       response = self.client_stub.post('/create',data = {'first_name' 'tester_first_n@me''last_name':'test''email':'tester@test.com''address':'5678 everywhere''city':'far from here''state':'CA''country':'USA''number':'987654321'})
    22       self.assertEqual(response.status_code, 200)
    23   def tearDown(self):
    24       self.phone.delete()
    25       self.person.delete()

     

    兩個測試會失敗。

    咱們要怎麼作才能讓測試經過呢?首先咱們要爲添加數據到數據庫增長一個視圖功能來查看。

  2. 添加路徑:

    1 url(r'^create$', create),

     

  3. 更新「views.py」:

    1 def create(request):
    2   form = ContactForm(request.POST)if form.is_valid():
    3     form.save()
    4     return HttpResponseRedirect('all/')return render(request, 'add.html', {'person_form' : form}, context_instance = RequestContext(request))

     

  4. 再次測試:

    1 $ python manage.py test user_contacts

     

    此次只有一個測試會失敗 - AssertionError: 302 != 200 - 由於咱們嘗試添加一些不經過驗證的數據但添加成功了。換言之,咱們須要更新「models.py」文件中的表單都要把驗證考慮進去。

  5. 更新「models.py」:

    01 from django.db import modelsfrom user_contacts.validators import validate_string, validate_numberclass Person(models.Model):
    02    first_name = models.CharField(max_length = 30, validators = [validate_string])
    03    last_name = models.CharField(max_length = 30, validators = [validate_string])
    04    email = models.EmailField(null = True, blank = True)
    05    address = models.TextField(null = True, blank = True)
    06    city = models.CharField(max_length = 15, null = True,blank = True)
    07    state = models.CharField(max_length = 15, null = True, blank = True, validators = [validate_string])
    08    country = models.CharField(max_length = 15, null = True, blank = True)
    09  
    10    def __unicode__(self):
    11        return self.last_name +", "+ self.first_nameclass Phone(models.Model):
    12    person = models.ForeignKey('Person')
    13    number = models.CharField(max_length=10, validators = [validate_number])
    14  
    15    def __unicode__(self):
    16        return self.number

     

  6. 刪除當前的數據庫,「db.sqlite3」,從新同步數據庫:

    1 $ python manage.py syncdb

     

    再次設置一個管理員帳戶。

  7. 新增驗證,更新new_contact_form.py:

    01 import refrom django import formsfrom django.core.exceptions import ValidationErrorfrom user_contacts.models import Person, Phonefrom user_contacts.validators import validate_string, validate_numberclass ContactForm(forms.Form):
    02   first_name = forms.CharField(max_length=30, validators = [validate_string])
    03   last_name = forms.CharField(max_length=30, validators = [validate_string])
    04   email = forms.EmailField(required=False)
    05   address = forms.CharField(widget=forms.Textarea, required=False)
    06   city = forms.CharField(required=False)
    07   state = forms.CharField(required=False, validators = [validate_string])
    08   country = forms.CharField(required=False)
    09   number = forms.CharField(max_length=10, validators = [validate_number])
    10   def save(self):
    11       if self.is_valid():
    12           data = self.cleaned_data
    13           person = Person.objects.create(first_name=data.get('first_name'), last_name=data.get('last_name'),
    14               email=data.get('email'), address=data.get('address'), city=data.get('city'), state=data.get('state'),
    15               country=data.get('country'))
    16           phone = Phone.objects.create(person=person, number=data.get('number'))
    17           return phone

     

  8. 再次運行測試,7個測試會經過的。

  9. 如今,先脫離開TDD一下子。我想在客戶端添加一個額外的測試驗證。因此添加test_contact_form.py:

    1 from django.test import TestCasefrom user_contacts.models import Personfrom user_contacts.new_contact_form import ContactFormclass TestContactForm(TestCase):
    2   def test_if_valid_contact_is_saved(self):
    3       form = ContactForm({'first_name':'test''last_name':'test','number':'9999900000'})
    4       contact = form.save()
    5       self.assertEqual(contact.person.first_name, 'test')
    6   def test_if_invalid_contact_is_not_saved(self):
    7       form = ContactForm({'first_name':'tes&t''last_name':'test','number':'9999900000'})
    8       contact = form.save()
    9       self.assertEqual(contact, None)

     

  10. 運行測試,全部9個測試都經過了。耶!如今能夠提交代碼了。

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

功能測試的終極版

當單元測試已經完成了,咱們如今添加功能測試去保證應用程序能夠順利運行。希望因爲咱們的單元測試已經經過了,功能測試也不會有什麼問題。

  1. 添加一個新類到「tests.py」文件中:

    01 class UserContactTest(LiveServerTestCase):
    02  
    03   def setUp(self):
    04       self.browser = webdriver.Firefox()
    05       self.browser.implicitly_wait(3)
    06  
    07   def tearDown(self):
    08       self.browser.quit()
    09  
    10   def test_create_contact(self): 
    11       # user opens web browser, navigates to home page   
    12       self.browser.get(self.live_server_url + '/')
    13       # user clicks on the Persons link
    14       add_link = self.browser.find_elements_by_link_text('Add Contact')
    15       add_link[0].click()
    16       # user fills out the form
    17       self.browser.find_element_by_name('first_name').send_keys("Michael")
    18       self.browser.find_element_by_name('last_name').send_keys("Herman")
    19       self.browser.find_element_by_name('email').send_keys("michael@realpython.com")
    20       self.browser.find_element_by_name('address').send_keys("2227 Lexington Ave")
    21       self.browser.find_element_by_name('city').send_keys("San Francisco")
    22       self.browser.find_element_by_name('state').send_keys("CA")
    23       self.browser.find_element_by_name('country').send_keys("United States")
    24       self.browser.find_element_by_name('number').send_keys("4158888888")
    25       # user clicks the save button
    26       self.browser.find_element_by_css_selector("input[value='Add']").click()
    27       # the Person has been added
    28       body = self.browser.find_element_by_tag_name('body')
    29       self.assertIn('michael@realpython.com', body.text)
    30  
    31   def test_create_contact_error(self): 
    32       # user opens web browser, navigates to home page   
    33       self.browser.get(self.live_server_url + '/')
    34       # user clicks on the Persons link
    35       add_link = self.browser.find_elements_by_link_text('Add Contact')
    36       add_link[0].click()
    37       # user fills out the form
    38       self.browser.find_element_by_name('first_name').send_keys("test@")
    39       self.browser.find_element_by_name('last_name').send_keys("tester")
    40       self.browser.find_element_by_name('email').send_keys("test@tester.com")
    41       self.browser.find_element_by_name('address').send_keys("2227 Tester Ave")
    42       self.browser.find_element_by_name('city').send_keys("Tester City")
    43       self.browser.find_element_by_name('state').send_keys("TC")
    44       self.browser.find_element_by_name('country').send_keys("TCA")
    45       self.browser.find_element_by_name('number').send_keys("4158888888")
    46       # user clicks the save button
    47       self.browser.find_element_by_css_selector("input[value='Add']").click()
    48       body = self.browser.find_element_by_tag_name('body')
    49       self.assertIn('Invalid', body.text)

     

  2. 運行功能測試:

    1 $ python manage.py test ft

     

  3. 這裏咱們只測試咱們寫過的,以及從最終用戶角度來看已經被單元測試過的代碼。4個測試都將會經過。

  4. 最後,咱們經過添加如下功能到AdminTest類來保證咱們添加進去的驗證會應用到管理員面板中:

    01 def test_create_contact_admin_raise_error(self): 
    02   # # user opens web browser, navigates to admin page, and logs in    
    03   self.browser.get(self.live_server_url + '/admin/')
    04   username_field = self.browser.find_element_by_name('username')
    05   username_field.send_keys('admin')
    06   password_field = self.browser.find_element_by_name('password')
    07   password_field.send_keys('admin')
    08   password_field.send_keys(Keys.RETURN)
    09   # user clicks on the Persons link
    10   persons_links = self.browser.find_elements_by_link_text('Persons')
    11   persons_links[0].click()
    12   # user clicks on the Add person link
    13   add_person_link = self.browser.find_element_by_link_text('Add person')
    14   add_person_link.click()
    15   # user fills out the form
    16   self.browser.find_element_by_name('first_name').send_keys("test@")
    17   self.browser.find_element_by_name('last_name').send_keys("tester")
    18   self.browser.find_element_by_name('email').send_keys("test@tester.com")
    19   self.browser.find_element_by_name('address').send_keys("2227 Tester Ave")
    20   self.browser.find_element_by_name('city').send_keys("Tester City")
    21   self.browser.find_element_by_name('state').send_keys("TC")
    22   self.browser.find_element_by_name('country').send_keys("TCA")
    23   # user clicks the save button
    24   self.browser.find_element_by_css_selector("input[value='Save']").click()
    25   body = self.browser.find_element_by_tag_name('body')
    26   self.assertIn('Invalid', body.text)

     

  5. 運行它。會有5個測試經過。提交以後就能夠收工啦。

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

測試結構

TDD是一個強大的工具以及是開發週期的一部分,幫助開發人員將程序拆分紅小的、可讀性強的部分。這樣的組成部分能夠更容易編寫和修改。另外,有一套全面完整的測試組件,覆蓋了你代碼的全部功能,有助於確保新功能在實現的時候不會破壞現有的功能。

在這過程當中,功能測試是一個高層次的測試,重點放在了最終用戶的交互功能上。

同時,單元測試支持功能測試來測試代碼的每一個功能。請記住,由於單元測試一次僅需測一個產品特徵,因此它們更容易編寫,通常覆蓋性會更好些,也更容易調試。它們會運行很是快,因此你進行單元測試的次數每每會多於功能測試。

讓咱們來看看咱們的測試結構,看看咱們的單元測試是如何支持功能測試的:

test-structure

軒騏
軒騏
翻譯於 3個月前

1人頂

 

 翻譯的不錯哦!

總結

恭喜你,你完成了!接下來作什麼呢?

首先,我沒有100%地遵循TDD過程,這是沒有關係的。大部分用TDD進行開發的開發人員並不會始終堅持在每個狀況下都使用它。有時候,你爲了把事情作好而偏離它這個過程——這是徹底沒有問題的。若是你想重構代碼、過程使得它更好地遵循TDD過程,你也能夠這麼去作。事實上,這是一個很好的作法。

其次,思考一下我錯過的測試。肯定什麼地方以及何時去測試是困難的。這通常須要時間和大量的練習去把測試作好。我打算在個人下一篇文章中多留一些空白,來看看大家可否找到那些空白並添加測試。

最後,還記得TDD過程的最後一步嗎?這一步是相當重要的,由於它能夠幫助建立可讀性強的、可維護的代碼,你不只僅要如今理解這件事,在未來也要如此。當你從新看回你的代碼,思考下你結合起來的測試。此外,你應該添加哪些測試來確保全部寫過的代碼都被測試?例如你能夠測試空值或者服務端的驗證。你也能夠在準備寫新代碼前去重構以前沒時間去整理的代碼。或許這是另一篇博文?思考下糟糕的代碼如何污染整個過程?

感謝閱讀。點擊這裏獲取最終的代碼。有任何的問題請在下面評論。

相關文章
相關標籤/搜索