准备工作:
1,安装Firefox
安装geckodriver
首先通过brew 安装
$ brew install geckodriver
然后设置配置文件~/.bash_profile文件
export PATH=$PATH:'/usr/local/Cellar/geckodriver/0.23.0/bin'
这里的路径一直到geckodriver下的bin目录
在安装完成后,终端会输出你的路径,在原来的路径后面加上bin就可以了。
最后执行代码让修改生效
source ~/.bash_profile
2,安装virtualenvwrapper
创建虚拟环境TDD:mkvirtualenv TDD
3,安装Django 1.11 和 selenium 3:
pip install 'django<1.12' 'selenium<4'
1.1
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://localhost:8000')
assert 'Django' in browser.title
运行结果
selenium.common.exceptions.WebDriverException: Message: Reached error page: about:neterror?e=connectionFailure&u=http%3A//localhost%3A8000/&c=UTF-8&f=regular&d=Firefox%20%E6%97%A0%E6%B3%95%E5%BB%BA%E7%AB%8B%E5%88%B0%20localhost%3A8000%20%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E8%BF%9E%E6%8E%A5%E3%80%82
1.2 让Django运行起来
django-admin.py startproject TDDweb
cd TDDweb
python manage.py runserver
1.3 创建Git仓库
在TDDweb目录中:git init .
数据库文件sqlite3和selenium日志geckodriver.log加入 .gitignore中忽略变动
echo 'db.sqlites' >> .gitignore
echo 'geckodriver' >> .gitignore
Git add . 添加当前文件夹中其他内容
git status 查看状态
发现pyc文件
git rm -r --cached TDDweb/__pycache__ 删掉所有pyc
echo '__pycache__' >> .gitignore
echo '*.pyc' >> .gitignore 加入忽略文件
git status 发现忽略文件变化
Git add .gitignore
git commit -m 'first commit' 提交
使用selenium驱动真实的浏览器进行测试,即从用户角度查看应用的运作,称之为功能测试。
功能测试=验收测试=端到端测试
用户故事:
from selenium import webdriver
browser = webdriver.Firefox()
#乔伊听说有一个很酷的在线待办事项应用
#她去看了这个应用的首页
browser.get('http://localhost:8000')
#她注意到网页的标题和头部包含'To-Do'这个词
assert 'To-Do' in browser.title
#应用邀请她输入一个待办事项
#她在文本中输入了'Buy peacock feathers'(购买孔雀羽毛)
#她的爱好是钓鱼
#她输入回车键后页面刷新了
#待办事项显示'1:Buy peacock feathers'
#页面又显示了一个文本框,可以输入其他待办事项
#她输入了'Use peacock feathers to make a fly'(做假蝇)
#她做事非常有条理
#页面再次刷新,她的清单显示两条事项
#她想知道这个网站是否会记住她的清单
#她看到网站为她生成了一个唯一的URL
#而且页面中有一个文字解说这个功能
#她访问那个URL,发现她的清单还在
#她很满意,去休息了
browser.quit()
2.2 使用unittest模块
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author : Marvin King
# Date : 2019-01-31
from selenium import webdriver
import unittest
class NewVisitorTest(unittest.TestCase):
def setUp(self): #测试执行前
self.browser = webdriver.Firefox()
def tearDown(self): #测试方法执行后
self.browser.quit()
def test_can_start_a_list_and_retrieve_it_later(self): #测试方法
# 乔伊听说有一个很酷的在线待办事项应用
# 她去看了这个应用的首页
self.browser.get('http://localhost:8000')
# 她注意到网页的标题和头部包含'To-Do'这个词
self.assertIn('To-Do',self.browser.title) #测试失败执行
self.fail('Finish the test!')
# 应用邀请她输入一个待办事项
# 她在文本中输入了'Buy peacock feathers'(购买孔雀羽毛)
# 她的爱好是钓鱼
# 她输入回车键后页面刷新了
# 待办事项显示'1:Buy peacock feathers'
# 页面又显示了一个文本框,可以输入其他待办事项
# 她输入了'Use peacock feathers to make a fly'(做假蝇)
# 她做事非常有条理
# 页面再次刷新,她的清单显示两条事项
# 她想知道这个网站是否会记住她的清单
# 她看到网站为她生成了一个唯一的URL
# 而且页面中有一个文字解说这个功能
# 她访问那个URL,发现她的清单还在
# 她很满意,去休息了
if __name__ == '__main__':
unittest.main(warnings='ignore') #禁止抛出ResourceWarning
AssertionError: 'To-Do' not found in 'Welcome to Django'
2.3 提交
git status查看状态发现只有functional_tests.py产生变化
git diff :查看与上次提交的变化
Git commit -a :自动添加已追踪文件(没有增加新文件)。
3.1 第一个Django应用,第一个单元测试
Django鼓励以应用的方式组织带了吗。
创建应用--待办事项清单:
python manage.py startapp lists
3.2 单元测试与功能测试的区别
功能测试是站在用户角度从外部测试应用,单元测试站在程序员角度从内部测试应用。
同时使用两种测试的工作流程:
(1)先写功能测试,从用户角度描述应用的新功能。
(2)功能测试失败后,想办法写代码让它通过(或者说至少让挡墙失败的测试通过)。此时,使用一个或多个单元测试定义希望代码实现的效果,保证为应用中的每一行代码(只少)编写一个单元测试。
(3)单元测试失败后,编写最少量的应用代码,刚好让单元测试通过。有时,要在第2步和第3步之间多往复,直到我们觉得功能测试有一点进展为止。
(4)然后,再次运行功能测试,看能否通过,或者有没有进展。这一步可能促使我们编写一些新的单元测试和代码等。
3.3Django中的单元测试
lists/tests.py:
from django.test import TestCase
class SmokeTest(TestCase):
def test_bad_maths(self):
self.assertEqual(1 + 1, 3)
运行测试:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_bad_maths (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/.../TDDweb/lists/tests.py", line 7, in test_bad_maths
self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Destroying test database for alias 'default'...
git status
git add lists
git diff --staged
git commit -m 'Add app for lists,with deliberrately failing unit test'
3.4 Django中的MVC、URL和视图函数
测试:
from django.urls import resolve
from django.test import TestCase
from lists.views import home_page
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/') #解析url,映射到响应的视图函数
self.assertEqual(found.func,home_page)
ImportError: cannot import name 'home_page'
3.5 编写应用代码
lists/views.py
from django.shortcuts import render
home_page = None
django.urls.exceptions.Resolver404: {'tried': [[
3.6 urls.py
测试表明需要一个url映射。
TDDweb/urls.py
from django.conf.urls import url
from django.contrib import admin
from lists import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$',views.home_page,name='home')
]
raise TypeError('view must be a callable or a list/tuple in the case of include().')
修改views
def home_page():
pass
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
通过测试
git commit -am 'Fist unit test and url mapping, dummy view' #-am 添加所有已追踪文件中的改动,而且使用命令行中输入的提交消息
3.7 为视图编写单元测试
from django.urls import resolve
from django.test import TestCase
from lists.views import home_page
from django.http import HttpRequest
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/') #解析url,映射到响应的视图函数
self.assertEqual(found.func,home_page)
def test_home_page_returns_correct_html(self):
request=HttpRequest()
response = home_page(request)
html = response.content.decode('utf8')
self.assertTrue(html.startswith(''))
self.assertIn('To-Do lists ',html)
self.assertTrue(html.endswith(''))
TypeError: home_page() takes 0 positional arguments but 1 was given
“单元测试/编写代码”循环
小幅改动:
views.py
def home_page(request):
pass
继续测试:
AttributeError: 'NoneType' object has no attribute 'content'
小幅改动:
views.py
from django.http import HttpResponse
def home_page(request):
return HttpResponse()
继续测试:
self.assertTrue(html.startswith(''))
AssertionError: False is not true
继续改动:
def home_page(request):
return HttpResponse('')
测试:
self.assertIn('
AssertionError: '
继续:
def home_page(request):
return HttpResponse('To-Do lists ')
self.assertTrue(html.endswith(''))
AssertionError: False is not true
def home_page(request):
return HttpResponse('To-Do lists ')
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
单元测试通过
下面启动服务器进行功能测试
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "functional_tests.py", line 21, in test_can_start_a_list_and_retrieve_it_later
self.fail('Finish the test!')
AssertionError: Finish the test!
----------------------------------------------------------------------
Ran 1 test in 3.799s
功能测试完成
git commit -am 'Basic view now returns minimal HTML'
git log --oneline
d72e827 Basic view now returns minimal HTML
7ea9484 Fist unit test and url mapping, dummy view
0f12b62 Add app for lists,with deliberrately failing unit test
222dadb first use unittest
a1150e9 first commit
4.2 使用Selenium测试用户交互
from selenium import webdriver
import unittest
from selenium.webdriver.common.keys import Keys
import time
class NewVisitorTest(unittest.TestCase):
def setUp(self): # 测试执行前
self.browser = webdriver.Firefox()
def tearDown(self): # 测试方法执行后
self.browser.quit()
def test_can_start_a_list_and_retrieve_it_later(self): # 测试方法
# 乔伊听说有一个很酷的在线待办事项应用
# 她去看了这个应用的首页
self.browser.get('http://localhost:8000')
# 她注意到网页的标题和头部包含'To-Do'这个词
self.assertIn('To-Do', self.browser.title) # 测试失败执行
header_text = self.browser.find_element_by_tag_name('h1').text
self.assertIn('To-Do', header_text)
# 应用邀请她输入一个待办事项
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'Enter a to-do item'
)
# 她在文本中输入了'Buy peacock feathers'(购买孔雀羽毛)
# 她的爱好是钓鱼
inputbox.send_keys('Buy peacock feathers')
# 她输入回车键后页面刷新了
# 待办事项显示'1:Buy peacock feathers'
inputbox.send_keys(Keys.ENTER)
time.sleep(1)
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertTrue(
any(row.text == '1: Buy peacock feathers' for row in rows)
)
# 页面又显示了一个文本框,可以输入其他待办事项
# 她输入了'Use peacock feathers to make a fly'(做假蝇)
# 她做事非常有条理
self.fail('Finish the test!')
# 页面再次刷新,她的清单显示两条事项
# 她想知道这个网站是否会记住她的清单
# 她看到网站为她生成了一个唯一的URL
# 而且页面中有一个文字解说这个功能
# 她访问那个URL,发现她的清单还在
# 她很满意,去休息了
if __name__ == '__main__':
unittest.main(warnings='ignore') # 禁止抛出ResourceWarning
结果:
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: h1
提交:
git commit -am 'Functional test now checks we can input to-do item'
4.3 遵守不测试常量规则,使用模板解决这个问题
创建“lists/templates/home.html”
修改views.py
from django.shortcuts import render
def home_page(request):
return render(request, 'home.html')
进行单元测试:python manage.py test
raise TemplateDoesNotExist(template_name, chain=chain)
django.template.exceptions.TemplateDoesNotExist: home.html
settings注册app
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'lists',
]
继续单元测试发现:
self.assertTrue(html.startswith(''))
AssertionError: False is not true
主要看模板最后是不是空行(\n)
修改:
self.assertTrue(html.strip().endswith(''))
通过测试
重构:test.py
from django.test import TestCase
class HomePageTest(TestCase):
def test_use_home_template(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'home.html')
git add . 追踪templates
git commit -m 'Refactor home page view touse atemplate'
4.5 修改首页
home.html中加入
Your To-Do list
功能测试:python3 functional_tests.py
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="id_new_item"]
加入
功能测试:
AssertionError: '' != 'Enter a to-do item'
继续修改:
继续测试:
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="id_list_table"]
加入:
测试:
any(row.text == '1: Buy peacock feathers' for row in rows)
AssertionError: False is not true
修改:
self.assertTrue(
any(row.text == '1: Buy peacock feathers' for row in rows),
'New To-Do item did not appear in table'
)
'New To-Do item did not appear in table'
AssertionError: False is not true : New To-Do item did not appear in table
提交:git commit -am 'Front page HTML now generated from a template'
4.6 TDD流程
5.1 编写表单,发送POST请求
To-Do lists
Your To-Do list
功能测试:python3 functional_tests.py
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="id_list_table"]
延长time.sleep时长,观察网页的调试会发现是CSRF的问题。(settings注册了CSRF中间件)
'New To-Do item did not appear in table'
AssertionError: False is not true : New To-Do item did not appear in table
5.2 在服务器中处理POST请求
list/tests.py
from django.test import TestCase
class HomePageTest(TestCase):
def test_use_home_template(self):
response = self.client.get('/')
self.assertTemplateUsed(response, 'home.html')
def test_can_save_a_POST_request(self):
response = self.client.post('/', data={'item_text': 'A new list item'})
self.assertIn('A new list item', response.content.decode())
单元测试:python3 manage.py test
self.assertIn('A new list item', response.content.decode())
AssertionError: 'A new list item' not found in '\n
\n
结果符合预期
先编写一个简单的返回让测试通过
from django.shortcuts import render
from django.http import HttpResponse
def home_page(request):
if request.method == 'POST':
return HttpResponse(request.POST['item_text'])
return render(request, 'home.html')
5.3 把PYTHON变量传入模板中渲染
home.html
To-Do lists
Your To-Do list
{{ new_item_text }}
调整list/tests.py
def test_can_save_a_POST_request(self):
response = self.client.post('/', data={'item_text': 'A new list item'})
self.assertIn('A new list item', response.content.decode())
self.assertTemplateUsed('response','home.html')
AssertionError: No templates used to render the response
views.py
from django.shortcuts import render
def home_page(request):
return render(request,'home.html',{'new_item_text':request.POST['item_text']})
单元测试:
django.utils.datastructures.MultiValueDictKeyError: "'item_text'"
from django.shortcuts import render
def home_page(request):
return render(request,'home.html',{'new_item_text':request.POST.get('item_text','')})
单元测试通过
功能测试:
AssertionError: False is not true : New To-Do item did not appear in table
functional_tests.py代码改进:
(1)
self.assertTrue(
any(row.text == '1: Buy peacock feathers' for row in rows),
f'New To-Do item did not appear in table。Contents were:\n{table.text}'
)
3.6的新特性,字符串前加f,里面就可以用“{}”内加 局部变量
AssertionError: False is not true : New To-Do item did not appear in table。Contents were:
Buy peacock feathers
(2)
self.assertIn('1: Buy peacok feathers' , [row.text for row in rows])
AssertionError: '1: Buy peacock feathers' not found in ['Buy peacock feathers']
手动加上‘1:’ :
1: {{ new_item_text }}
通过功能测试。
继续编写功能测试:
# 页面又显示了一个文本框,可以输入其他待办事项
# 她输入了'Use peacock feathers to make a fly'(做假蝇)
# 她做事非常有条理
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Use peacock feathers to make a fly')
inputbox.send_keys(Keys.ENTER)
time.sleep(1)
# 页面再次刷新,她的清单显示两条事项
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertIn('1: Buy peacock feathers', [row.text for row in rows])
self.assertIn('2: Use peacock feathers to make a fly ', [row.text for row in rows])
# 她想知道这个网站是否会记住她的清单
# 她看到网站为她生成了一个唯一的URL
# 而且页面中有一个文字解说这个功能
self.fail('Finish the test!')
AssertionError: '1: Buy peacock feathers' not found in ['1: Use peacock feathers to make a fly']
5.4 事不过三,三则重构
重构之前要提交
test开头的方法作为测试方法,其他方法作为辅助方法,辅助方法尽量放在测试方法之前。
def tearDown(self): # 测试方法执行后
self.browser.quit()
def check_for_row_in_list_table(self, row_text):
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertIn(row_text, [row.text for row in rows])
def test_can_start_a_list_and_retrieve_it_later(self): # 测试方法
# 乔伊听说有一个很酷的在线待办事项应用
# 她去看了这个应用的首页
self.browser.get('http://localhost:8000')
# 她注意到网页的标题和头部包含'To-Do'这个词
self.assertIn('To-Do', self.browser.title) # 测试失败执行
header_text = self.browser.find_element_by_tag_name('h1').text
self.assertIn('To-Do', header_text)
# 应用邀请她输入一个待办事项
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'Enter a to-do item'
)
# 她在文本中输入了'Buy peacock feathers'(购买孔雀羽毛)
# 她的爱好是钓鱼
inputbox.send_keys('Buy peacock feathers')
# 她输入回车键后页面刷新了
# 待办事项显示'1:Buy peacock feathers'
inputbox.send_keys(Keys.ENTER)
time.sleep(1)
self.check_for_row_in_list_table('1: Buy peacock feathers')
# 页面又显示了一个文本框,可以输入其他待办事项
# 她输入了'Use peacock feathers to make a fly'(做假蝇)
# 她做事非常有条理
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Use peacock feathers to make a fly')
inputbox.send_keys(Keys.ENTER)
time.sleep(1)
# 页面再次刷新,她的清单显示两条事项
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.check_for_row_in_list_table('1: Buy peacock feathers')
self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')
AssertionError: '1: Buy peacock feathers' not found in ['1: Use peacock feathers to make a fly']
测试一致,证明完成重构,做一次提交:git commit -am '第一次重构完成'
5.5 Django ROM 第一个模型
list/tests.py
from lists.models import Item
[...]
class ItemModelTest(TestCase):
def test_saving_and_retrieving_items(self):
first_item = Item()
first_item.text = 'The first (ever) list item'
first_item.save()
second_item = Item()
second_item.text = 'Item the second'
second_item.save()
saved_items = Item.objects.all()
self.assertEqual(saved_items.count(), 2)
first_saved_item = saved_items[0]
second_saved_item = saved_items[1]
self.assertEqual(first_saved_item.text, 'The first (ever) list item')
self.assertEqual(second_saved_item.text, 'Item the second')
单元测试:
ImportError: cannot import name 'Item'
list/models.py
from django.db import models
class Item:
pass
继续单元测试:
AttributeError: 'Item' object has no attribute 'save'
实现save方法:
from django.db import models
class Item(models.Model):
pass
继续单元测试:
django.db.utils.OperationalError: no such table: lists_item
5.5.1 第一个数据迁移
ORM的任务是模型化数据库。创建数据库是由迁移负责。迁移 的任务是根据models.py的变化,变动数据库的表。
创建迁移:python manage.py makemigrations
Migrations for 'lists':
lists/migrations/0001_initial.py
- Create model Item
再次测试:
AttributeError: 'Item' object has no attribute 'text'
修改models.py
class Item(models.Model):
text = models.TextField()
django.db.utils.OperationalError: no such column: lists_item.text
5.5.3 添加新字段就要创建新迁移
You are trying to add a non-nullable field 'text' to item without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option: 2
这个命令不允许添加没有默认值的列。
class Item(models.Model):
text = models.TextField(default='')
lists/migrations/0002_item_text.py
- Add field text to item
测试通过
git add lists
git commit -m 'Models for list Items and associated migration'
5.6 把POST请求中的数据存入数据库
增加tests.py代码
def test_can_save_a_POST_request(self):
response = self.client.post('/', data={'item_text': 'A new list item'})
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new list item')
self.assertIn('A new list item', response.content.decode())
self.assertTemplateUsed(response, 'home.html')
代码异味:POST测试请求太长
AssertionError: 0 != 1
修改视图:
from django.shortcuts import render
from lists.models import Item
def home_page(request):
item = Item()
item.text = request.POST.get('item_text', '')
item.save()
return render(request, 'home.html', {'new_item_text': request.POST.get('item_text', '')})
需要进一步处理:
1、代码异味:POST测试请求太长
2、每次请求都会保存一个空白的待办事项
return render(request, 'home.html', {'new_item_text': item.text})
处理2、每次请求都会保存一个空白的待办事项:
class HomePageTest(TestCase):
[...]
def test_only_saves_items_when_necessary(self):
self.client.get('/')
self.assertEqual(Item.objects.count(), 0)
AssertionError: 1 != 0
修改视图:
def home_page(request):
if request.method == 'POST':
new_item_text = request.POST['item_text']
Item.objects.create(text=new_item_text) #简化Item对象的创建,无需使用save
else:
new_item_text = ''
return render(request, 'home.html', {'new_item_text': new_item_text})
通过测试
5.7 处理POST请求后重定向
def test_can_save_a_POST_request(self):
response = self.client.post('/', data={'item_text': 'A new list item'})
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new list item')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/')
AssertionError: 200 != 302
清理视图:
from django.shortcuts import render, redirect
from lists.models import Item
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/')
return render(request, 'home.html')
通过单元测试
更好的单元测试实践方法:一个测试只测试一件事情
def test_can_save_a_POST_request(self):
response = self.client.post('/', data={'item_text': 'A new list item'})
self.assertEqual(Item.objects.count(), 1)
new_item = Item.objects.first()
self.assertEqual(new_item.text, 'A new list item')
def test_redirects_after_POST(self):
response = self.client.post('/', data={'item_text': 'A new list item'})
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/')
Ran 5 tests in 0.015s
OK
5.8 在模板中渲染待办事项
待处理事项:
在表格中显示多个待办事项
支持多个清单
设置-使用-断言 : 测试的典型结构
def test_display_all_list(self):
Item.objects.create(text='itemey 1')
Item.objects.create(text='itemey 2')
response = self.client.get('/')
self.assertIn('itemey 1',response.content.decode())
self.assertIn('itemey 2',response.content.decode())
AssertionError: 'itemey 1' not found in '\n
\n修改模板和视图
{% for item in items %}
1: {{ item.text }}
{% endfor %}
from django.shortcuts import render, redirect
from lists.models import Item
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/')
items = Item.objects.all()
return render(request, 'home.html',{'items':items})
Ran 6 tests in 0.021s
OK
单元测试通过
功能测试:AssertionError: 'To-Do' not found in 'OperationalError at /'
5.9 使用迁移创建生产数据库
Django为单元测试创建了一个专用的测试数据库。所以下一步要创建一个真正的数据库通过功能测试。
默认使用sqlite3
创建数据库:python manage.py migrate
功能测试:
AssertionError: '2: Use peacock feathers to make a fly ' not found in ['1: bsdfa', '1: Buy peacock feathers', '1: Use peacock feathers to make a fly']
{% for item in items %}
{{ forloop.counter }}: {{ item.text }}
{% endfor %}
AssertionError: '1: Buy peacock feathers' not found in ['1: bsdfa', '2: Buy peacock feathers', '3: Use peacock feathers to make a fly', '4: Buy peacock feathers']
每次测试都会在数据库中遗留数据。
手动清理:先删除数据库,再创建
rm db.sqlite3
python manage.py migrate --noinput
self.fail('Finish the test!')
AssertionError: Finish the test!
功能测试通过
待处理事项:
功能测试之后删除测试数据
支持多个清单
提交:
git add lists
git commit -m 'Redirect after POST, and show all items in template'
6.1 确保功能测试之间相互隔离
将功能测试的运行方式改为单元测试相同的运行方式
mkdir functional_tests
touch functional_tests/__init__.py
git mv functional_tests.py functional_tests/tests.py
现在运行功能测试的方式改为:Python manage.py test functional_tests
修改functional_tests/tests.py
删掉main
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
class NewVisitorTest(LiveServerTestCase):
def setUp(self): # 测试执行前
self.browser = webdriver.Firefox()
def test_can_start_a_list_and_retrieve_it_later(self): # 测试方法
# 乔伊听说有一个很酷的在线待办事项应用
# 她去看了这个应用的首页
self.browser.get(self.live_server_url)
运行:Python manage.py test functional_tests
没有问题,提交
git status
git add functional_tests
git diff --staged -M
git commit -m 'make functional_tests as app ,use LiveServerTestCase'
运行python manage.py test 会运行所有的测试APP
如果只想运行单元测试:Python manage.py test lists
6.2 升级selenium和Geckodriver
pip install --upgrade selenium
下载新版geckodriver,备份旧版,执行geckodriver --version 查看是否可用
6.3 隐式等待、显示等待和含糊的time.sleep
selenium内置了显示等待和隐式等待,隐式等待不建议使用。
from selenium.common.exceptions import WebDriverException
MAX_WAIT = 10
class NewVisitorTest(LiveServerTestCase):
def setUp(self): # 测试执行前
self.browser = webdriver.Firefox()
def tearDown(self): # 测试方法执行后
self.browser.quit()
def wait_for_row_in_list_table(self, row_text):
start_time = time.time()
while True:
try:
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertIn(row_text, [row.text for row in rows])
return
except (AssertionError, WebDriverException) as e:
if time.time() - start_time > MAX_WAIT:
raise e
time.sleep(0.5)
删除time.sleep()
# 她输入回车键后页面刷新了
# 待办事项显示'1:Buy peacock feathers'
inputbox.send_keys(Keys.ENTER)
self.wait_for_row_in_list_table('1: Buy peacock feathers')
# 页面又显示了一个文本框,可以输入其他待办事项
# 她输入了'Use peacock feathers to make a fly'(做假蝇)
# 她做事非常有条理
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Use peacock feathers to make a fly')
inputbox.send_keys(Keys.ENTER)
time.sleep(1)
# 页面再次刷新,她的清单显示两条事项
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.wait_for_row_in_list_table('1: Buy peacock feathers')
self.wait_for_row_in_list_table('2: Use peacock feathers to make a fly')
运行:Python manage.py test
破坏测试:
(1)
rows = table.find_elements_by_tag_name('tr')
self.assertIn('foo', [row.text for row in rows])
AssertionError: 'foo' not found in ['1: Buy peacock feathers']
(2)
table = self.browser.find_element_by_id('id_nothing')
rows = table.find_elements_by_tag_name('tr')
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: [id="id_nothing"]
还原,测试一下看看有没有问题
AssertionError: Finish the test!