使用Selenium测试用户交互

如果用绳子从井 里提一桶水,井不太深,而且桶不是很满,提起来很容易。就算提满满一桶水,刚开始也很容易。但用不了多久你就累了。TDD 理念好比是一个棘轮,你可以使用它保存当前的进度,休息一会儿,而且能保证进度绝不倒退。

上一章,我们进行到哪里了,忘记了,可以执行命令来看看
注意:从本章开始,我的目录从lists改为list,因为之前的环境不小心删掉了,从新搞了遍环境

进入目录运行 functional_test.py

$ python functional_tests.py

如果这里报错,提示说加载页面出错或者无法连接。
这是因为运行测试之前没有使用manage.py runserver 启动开发服务器。
启动服务

python manage.py runserver

TDD 的优点之一是,永远不会忘记接下该做什么——重新运行测试就知道要接下来要做的事情。


断言报错返回,Finish the test! 。
我们打开并编辑functional_tests.py 文件,完成功能测试用例

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import unittestclass NewVisitorTest(unittest.TestCase):def setUp(self):self.brower = webdriver.Firefox()def tearDown(self):self.brower.quit()def test_can_start_a_list_and_retrieve_it_later(self):# 伊迪丝听说有一个很酷的在线待办事项应用# 她去看了这个应用的首页self.brower.get('http://localhost:8000')# 她注意到网页的标题和头部都包含“To-Do”这个词self.assertIn('To-Do', self.brower.title)header_test = self.brower.find_element_by_tag_name('h1').text  # 1self.assertIn('To-Do', header_test)# 应用邀请她输入一个待办事项inputbox = self.brower.find_element_by_id('id_new_item')  # 1self.assertEqual(inputbox.get_attribute('placeholder'),'Enter a to-do item')# 她在一个文本框中输入了“Buy peacock feathers”(购买孔雀羽毛)# 伊迪丝的爱好是使用假蝇做鱼饵钓鱼inputbox.send_keys('Buy peacock feathers')  # 2# 她按回车键后,页面更新了# 待办事项表格中显示了“1: Buy peacock feathers”inputbox.send_keys(Keys.ENTER)  # 3time.sleep(1)  # 4table = self.brower.find_element_by_id('id_list_table')rows = table.find_element_by_tag_name('tr')  # 1self.assertTrue(any(row.text == '1: Buy peacock feathers' for row in rows))# 页面中又显示了一个文本框,可以输入其他的待办事项# 她输入了“Use peacock feathers to make a fly”(使用孔雀羽毛做假蝇)# 伊迪丝做事很有条理# 页面再次更新,她的清单中显示了这两个待办事项# 伊迪丝想知道这个网站是否会记住她的清单# 她看到网站为她生成了一个唯一的URLself.fail('Finish the test!')# 页面再次更新,她的清单中显示了这两个待办事项# 伊迪丝想知道这个网站是否会记住她的清单# 她看到网站为她生成了一个唯一的URLif __name__ == '__main__':unittest.main(warnings='ignore')

注释里用到的方法如下

  1. 使用了Selenium提供的几个方法用来查找网页的内容:
    find_element_by_tag_name
    find_elements_by_tag_name
    注意第二个元素多了一个s会返回多个元素,也可能返回一个空的列表

  2. 使用了Selenium提供的send_keys方法用来在输入框输入内容

  3. Keys这个类(需要导入) 的作用是让我们发送回车键的按键

  4. 按下回车键会刷新。time.sleep(),表示暂停几秒钟

  5. any函数,这是Python里的原生函数

    any() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。

继续运行python functional_tests.py,报错:Message: Unable to locate element: h1。找不到h1元素

在对Web应用修改之前,我们进行一次提交

$ git diff # 会显示对functional_tests.py的改动
$ git commit -am "Functional test now checks we can input a to-do item"

遵守“不测试常量”规则,使用模板解决这个问题

我们去看下list/tests.py中的单元测试。测试时要查找特定的HTML字符串,但是这个方法效率很低。所以,单元测试的规则之一是不测试常量,我们以文本形式测试HTML在很大程度上就是测试常量
如果有以下的代码:

   wibble = 3

在测试时,就没有必要按照以下方法写

from myprogram import wibble
assert wibble == 3

单元测试的本质是逻辑、流程控制和配置。我们编写断言测试HTML字符串中是否有指定的字符串,这个不是单元测试应该做的

在Python代码中插入原始字符串需要是处理HTML错误的。我们有更好的方法,使用模板。如果把HTML放在一个扩展名为.html文件中。Python领域有很多模板框架,Django也有自己的模板系统,而且很好用,我们来看看

使用模板重构

让视图函数返回完全一样的HTML,但是使用不同的处理方式。这个过程叫作重构,在功能不变的前提下改进代码
重构的原则是不能没有测试。我们在做测试驱动开发,测试已经有了,现在只需要检查下测试是否通过,测试通过后才能保证重构前后的表现一致
先把HTML 字符串提取出来写入单独的文件。创建模板的文件夹list/templates并新建文件list/templates/home.html,再把HTML 写入这个文件
这里我用的notepad++有高亮语法

<html><title>To-Do list</title>
</html>


接下来,我们修改视图,让它去调用home.html:

from django.shortcuts import renderdef home_page(request):return render(request,'home.html')


现在我们不用自己构建HttpResponse对象,可以使用Django中的render函数。

render

第一个参数是请求对象(后面介绍)
第二个参数是渲染的模板名
Django会自动在所有的应用目录中搜索名为templates的文件夹,然后根据模板中的内容构建一个HttpResponse 对象

模板是Django 中一个很强大的功能,使用模板的主要优势之一是能把Python 变量代入HTML 文本
这也是为什么使用render 和render_to_string(稍后用到),而不用原生的open 函数手动从硬盘中读取模板文件的原因

现在我们测试下模板,看下模板是否生效

$ python manage.py test


运行报错:TemplateDoesNotExist: home.html

我们刚才有写home.html模板并且放在了list/templates文件夹中。找不到的原因是没有在Django中注册list应用,需要用startapp 命令添加应用
执行startapp 命令后,还需要配置应用到文件settings.py 中,打开settings.py,找到变量INSTALLED_APPS,把list 目录加进去:

# Application definitionINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','list', # 添加
]

然后再运行测试

$ python manage.py test


测试通过,至此我们对代码做的小重构通过了测试,从现在开始我们不需要测试常量,只需要检查模板渲染是否正确

Django测试客户端

测试模板渲染是否正确,一种方法是在测试中手动渲染模板,然后与视图返回的结果做比较
在这里我们利用Django 提供的render_to_string 函数
打开并编辑单元测试文件list/tests.py

from django.template.loader import render_to_string
[...]
def test_home_page_returns_correct_html(self):
request = HttpRequest()
response = home_page(request)
html = response.content.decode('utf8')
expected_html = render_to_string('home.html')
self.assertEqual(html, expected_html)

运行测试,通过

$ python manage.py test


这样测试有点麻烦,测试时调用.decode() 和.strip()又测试太麻烦,所以选择使用Django自带的测试客户端(TestClient)检查使用那个模板的原生方式。
我们从新修改list/tests.py,来看看效果

class HomePageTest(TestCase):def test_root_url_resolves_to_home_page_view(self):found = resolve('/')self.assertEqual(found.func, home_page)def test_home_page_returns_correct_html(self):response = self.client.get('1')     # 1html = response.content.decode('utf8')  # 2self.assertTrue(html.startswith('<html>'))self.assertIn('<title>To-Do list</title>', html)self.assertTrue(html.strip().endswith('</html>'))self.assertTemplateUsed(response, 'home.html')  # 3
  1. 不用手动创建HttpRequest 对象,也不用直接调用视图函数,而是调用self.client.get方法直接传入要测试的URL
  2. 暂时保留
  3. .assertTemplateUsed 是Django TestCase 类提供的测试方法,用于检查响应是使用哪个模板渲染(注意,这个方法只能测试通过测试客户端获取的响应)

运行测试

$ python manage.py test


测试通过后,我们对我们的测试结果有疑虑,所以我们需要搞点小事情
打开并编辑单元测试文件list/tests.py

self.assertTemplateUsed(response, 'home.html')

修改为

self.assertTemplateUsed(response, 'wrong.html')

运行测试

$ python manage.py test


我们再将断言修改回去,并且对测试Case进行了精简,顺便把原来的 test_root_url_resolves 测试Case删除,因为Django的测试客户端已经后台测试过来了,我们将2个麻烦的测试Case精简成一个

from django.test import TestCaseclass HomePageTest(TestCase):def test_uses_home_template(self):response = self.client.get("/")self.assertTemplateUsed(response, "home.html")

运行测试

$ python manage.py test

关于重构

这个重构的例子很烦琐。但正如Kent Beck 在Test-Driven Development: By Example 一书中所说的:“我是推荐你在实际工作中这么做吗?不是。我只是建议你要知道怎么按照这种 方式做。”
其实,写这一部分时我的第一反应是先修改代码,直接使用assertTemplateUsed 函数,删除那三个多余的断言,只在渲染得到的结果中检查期望看到的内容,然后再修改代码。
但要注意,如果真这么做了可能就会犯错,因为我可能不会在模板中编写正确的 和 标签,而是随便写一些字符串。

重构后,我们做一次提交

$ git status # 会看到tests.py、views.py、settings.py以及新建的templates文件夹
$ git add . # 还会添加尚未跟踪的templates文件夹
$ git diff --staged # 审查我们想提交的内容
$ git commit -m "Refactor home page view to use a template"

Python 测试驱动开发(四)测试及重构的目的(上)相关推荐

  1. python测试驱动开发_Python测试驱动开发:使用Django、Selenium和JavaScript进行Web编程(第2版)...

    领取成功 您已领取成功! 您可以进入Android/iOS/Kindle平台的多看阅读客户端,刷新个人中心的已购列表,即可下载图书,享受精品阅读时光啦! - | 回复不要太快哦~ 回复内容不能为空哦 ...

  2. python测试驱动开发 中文版_GitHub - starryrbs/python_tdd: 使用Python测试驱动开发完成Django项目...

    我知道你会问:"你是谁,为什么要写这本书,我为什么要读这本书?" 我至今仍然处在编程事业的初期.人们说,不管从事什么工作,都要历经从新手到熟手的 过程,最终有可能成为大师.我要说的 ...

  3. python测试驱动开发_使用Python进行测试驱动开发的简单介绍

    python测试驱动开发 by Dmitry Rastorguev 德米特里·拉斯托格夫(Dmitry Rastorguev) 使用Python进行测试驱动开发的简单介绍 (A simple intr ...

  4. Python测试驱动开发(TDD)

    Python测试驱动开发(TDD) 前言:TDD是一种敏捷开发模式,而不是测试方法. 测试很难 --- 难在坚持,一直做下去. 现在花时间编写的测试不会立即显出功效,要等到很久以后才有作用 --- 或 ...

  5. Python 测试驱动开发读书笔记(准备工作)安装软件

    知识不是靠一篇或者几篇博客就能掌握的,它从来都不是轻松的 当明白这个道理时,我静下心来选择了<Python测试驱动开发>来学习,目前还用不上,但是这本书是今年前半年的计划,新工作也比较忙, ...

  6. Python 测试驱动开发读书笔记(三)使用单元测试测试简单的首页

    使用单元测试测试简单的首页 在上一章结尾,我们有一个简单的测试例子,但是这个例子执行是失败的 失败的原因是浏览器的首页标题不是To-Do,从这章开始编写这个应用 第一个Django应用,第一个单元测试 ...

  7. Python 测试驱动开发读书笔记(一)使用功能测试协助安装Django

    使用功能测试协助安装Django 本章节知识分为两部分 一是测试驱动开发(Test-Driven Development,TDD)的基础知识: 二是从零开始写一个Web的应用,并用Selenium进行 ...

  8. Python 测试驱动开发(四)测试及重构的目的(下)

    接着修改首页 运行 $ python functional_tests.py,我们的功能测试依然是失败的,所以需要继续修改代码,直至测试通过 现在我们的HTML是单独保存在模板里,所以可以放心的修改. ...

  9. Python 测试驱动开发(五)测试数据库(上)

    保存用户输入:测试数据库 要获取用户输入的待办事项,发送给服务器,这样才能使用某种方式保存待办事项,然后再显示给用户查看. TDD的重要思想是必要时一次只做一件事.每次只做必要的操作,让功能测试向前迈 ...

最新文章

  1. 《C++程序设计教程(第3版)》——第3章,第2节cout输出流
  2. 非静态方法可以调用静态变量吗
  3. 微信红利末期,新媒体运营除了打造10W+还应该做什么?
  4. base64的php文件上传,PHP传统文件上传和Base64位文件上传
  5. BufferedOutputStream_字节缓冲输出流
  6. ueditor 添加按钮不显示_不可思议按键精灵的按钮选择框居然这么简单
  7. ClassCastException:AdaptiveIconDrawable cannot be cast to BitmapDrawable
  8. javascript判断一个元素是另外一个元素的子元素
  9. LSTM block和cell区别
  10. html怎么用小小的图片铺满作为背景,多种背景图片随机切换的应用
  11. opencv 图像去雾
  12. IDEA 方法自动添加注释
  13. 【HTML——电脑病毒 特效(效果+代码)】
  14. 科普:前端是做什么的?工作内容有哪些?
  15. php之——常用的字符串函数
  16. Ftp站点访问及FileZilla客户端使用
  17. 太阳电池板特性实验_太阳电池伏安特性的测量
  18. pygame写简单推箱子游戏
  19. 原生小程序实现手风琴
  20. 在2022年需要使用的25个最佳GOOGLE CHROME扩展

热门文章

  1. Ruby学习笔记-循环与选择结构
  2. 堆内存里的各种奇怪填充值
  3. linux驱动编写(声卡驱动之asoc移植)
  4. 随想录(回乡的若干小事)
  5. 运筹学matlab实验报告,运筹学上机实验报告 利用Matlab求解整数线性规划
  6. python新手如何找工作最有效_Python好学吗?要学多久?
  7. jsonobject修改key的值_JSONObject(org.json)的一点修改
  8. python怎么定义全局变量_python中如何定义全局变量
  9. 路由交换基础——NAT(网络地址转换)
  10. SQL server 查询语句