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过程的最后一步吗?这一步是相当重要的,由于它能够帮助建立可读性强的、可维护的代码,你不只仅要如今理解这件事,在未来也要如此。当你从新看回你的代码,思考下你结合起来的测试。此外,你应该添加哪些测试来确保全部写过的代码都被测试?例如你能够测试空值或者服务端的验证。你也能够在准备写新代码前去重构以前没时间去整理的代码。或许这是另一篇博文?思考下糟糕的代码如何污染整个过程?

感谢阅读。点击这里获取最终的代码。有任何的问题请在下面评论。

相关文章
相关标签/搜索