使用单元测试测试简单的首页

在上一章结尾,我们有一个简单的测试例子,但是这个例子执行是失败的

失败的原因是浏览器的首页标题不是To-Do,从这章开始编写这个应用

第一个Django应用,第一个单元测试

执行命令创建一个新的工程应用,

$ python manage.py startapp lists

这个命令会在superlists 文件夹中创建子文件夹lists,与superlists 子文件夹相邻,并在lists中创建一些文件,用来保存模型、视图以及目前最关注的测试Case:

superlists/
├─ db.sqlite3
├─ functional_tests.py
├─ lists
│ ├─ admin.py
│ ├─ apps.py
│ ├─ __init__.py
│ ├─ migrations
│ │ └─ __init__.py
│ ├─ models.py
│ ├─ tests.py
│ └─ views.py
├─ manage.py
└─ superlists
├─ __init__.py
├─ __pycache__
├─ settings.py
├─ urls.py
└─ wsgi.py

单元测试与功能测试的区别

作者给出的区别是:

功能测试站在用户的角度从外部测试应用,单元测试则站在程序员的角度从内部测试应用。

如果采用TDD方法进行这两个不同类型的测试,采用的工作流程大致如下

(1) 先写功能测试Case,从用户的角度描述应用的新功能。
(2) 功能测试失败后,再去编写代码让它通过(或者说至少让当前失败的测试通过)。同时,使单元测试Case定义代码需要实现的功能,保证为应用中的每一行代码(至少)编写一个单元测试。
(3) 单元测试失败后,编写最少量的应用代码,刚好让单元测试通过。
(4) 然后,再去执行功能测试Case,看能否通过,或者有没有通过。这会促使我们编写一些新的单元测试和代码等。

从上面这里可以看的出来

功能测试是站在高层驱动开发,而单元测试则是从底层驱动开发

Django 中的单元测试

我们打开刚才新建的lists/tests.py文件

Tests.py是Django库提供的,是在标准版unittest.TestCase的增强版,也添加了一些Django的专用功能,后面会有介绍

我们已经知道了TDD循环从失败的测试开始,再去编写代码让测试通过

我们写一个错误的会失败的测试,打开lists/test.py

from django.test import TestCaseclass SmokeTest(TestCase):def test_bad_maths(self):# 断言是否正确,1+1 !=3,所以这个Case是失败的self.assertEqual(1+1, 3)


在superlists目录运行 python manage.py test,执行结果如下:

执行结果:失败,先放一放,后面再解决
执行结果虽然是失败的,但是单元测试是可以运行的,我们先提交代码,

$ git status # 会显示一个消息,说没跟踪lists/
$ git add lists # 这里的lists,第二次创建时,发现创建的是list,需要注意
$ git diff --staged # 显示将要提交的内容差异
$ git commit -m "Add app for lists, with deliberately failing unit test" # -m 的作用是让你在命令行中直接编写提交备注


Django中的MVC、URL和视图函数

Django 遵守了经典的模型- 视图- 控制器(Model-View-Controller,MVC)模式,但并没严格遵守Django有模型,视图其实像是控制器,模板其实才是视图。 Django 和服务器Web框架一样,主要任务是决定用户访问网站中的某个 URL 时做些什么

Django的工作流程有点儿类似下述过程。
(1) 针对某个URL 的HTTP 请求进入
(2) Django使用一些规则决定由哪个视图函数处理这个请求(这一步叫作解析URL)。
(3) 选中的视图函数处理请求,然后返回HTTP 响应

因此要测试两件事。
解析网站根路径(“/”)的URL,将其对应到我们编写的某个视图函数上?
让视图函数返回一些HTML,让功能测试通过?

现在我们打开 lists/tests.py,把之前错误的代码修改如下

from django.urls import resolve
from django.test import TestCase
from lists.views import home_page  # 2class HomePageTest(TestCase):def test_root_url_resolves_to_home_page_view(self):found = resolve('/') # 1self.assertEqual(found.func, home_page) # 1

➊ resolve 是Django 内部使用的函数,用于解析URL,并将其映射到相应的视图函数上。检查解析网站根路径“/”时,是否能找到名为home_page 的函数。
➋ 这个函数是什么?这是接下来要定义的视图函数,其作用是返回所需的HTML。从import 语句可以看出,要把这个函数保存在文件lists/views.py 中。

现在我们运行修改后的代码

$ python manage.py test


运行提示,无法找到“home_page”,这是因为我们导入了没有定义的函数,这个错误也是预期的,从TDD的思想看,功能测试和单元测试都失败了,我们就可以写应用代码了,让这些测试通过

编写应用代码

使用TDD 时要耐着性子,步步为营。尤其是学习和起步阶段,一次只能修改(或添加)一行代码。每一次修改的代码要尽量少,让失败的测试通过即可

失败的原因是没有找到要导入的home_page,我们现在修正这个问题
打开lists/views.py编写代码:

from django.shortcuts import renderhome_page = None

然后再执行python manage.py test


运行依然失败,查看错误日志发现问题是resolve()方法在解析’/‘时,Django报错404,也就是说Django 无法解析’/'URL的映射

urls.py

Django 用urls.py 文件把URL 映射到视图函数上。在文件夹superlists/superlists 中有个主urls.py文件,这个文件应用于整个网站。

url 条目的前半部分是正则表达式,定义适用于哪些URL。后半部分说明把请求发往何处:发给导入的视图函数,或是别处的urls.py 文件。

现在修改superlists/urls.py文件,文件里有使用说明,大家可以看一看,正则表达式是 ^$,表示空字符串,同时去掉 admin URL,因为暂时用不到 Django 的管理后台

from django.conf.urls import url
from list import viewsurlpatterns = [url(r'^$', views.home_page, name="home"),
]

结果依然报错,现在不显示404了,视图必须用可调用 列表/元组 在测试头文件里

view must be a callable or a list/tuple in the case of include()

单元测试把地址“/”和文件lists/views.py 中的home_page = None 连接起来了,home_page 无法调用。由此我们知道,要调整一下,把home_page 从None 变成真正的函数。

记住,每次改动代码都由测试驱使

我们继续修改lists /views.py,保存退出

from django.shortcuts import renderdef home_page():pass


然后再运行python manage.py test执行通过,第一条测试用例通过

至此,通过TDD思想,我们先后编写了功能/单元的测试Case,然后再通过测试Case去编写代码,其中使用了DjangoWeb应用框架,Unittest测试框架。
通过后,我们开始继续提交

$ git status
$ git diff  # 会显示urls.py、tests.py和views.py中的变动
$ git commit -am "First unit test and url mapping, dummy view"

为视图编写单元测试

下面我们为视图编写测试,我们不能调用什么都不能做的函数,而是自己定义一个函数,向浏览器返回HTML响应
打开lists/tests.py,添加新的测试Case

from django.urls import resolve
from django.test import TestCase
from django.http import HttpRequest
from lists.views import home_pageclass 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):request = HttpRequest()response = home_page(request)html = response.content.decode('utf8')self.assertTrue(html.startswith('<html>'))self.assertIn('<title>To-Do lists</title>', html)self.assertTrue(html.endswith('</html>'))

➊ 创建了一个HttpRequest 对象,用户在浏览器中请求网页时,Django 看到的就是 HttpRequest 对象。
➋ 把这个 HttpRequest 对象传给 home_page 视图,得到响应。响应对象是 HttpResponse 类的实例时,你应该不会觉得奇怪。
➌ 然后,提取响应的 .content。得到的结果是原始字节,即发给用户浏览器的 0 和 1。随 后,调用.decode(),把原始字节转换成发给用户的 HTML 字符串。
➍ 断言响应以 标签开头,并在结尾处关闭该标签。
➎ 断言响应中有一个 标签,其内容包含单词“To-Do lists”——因为在功能测 试中做了这项测试


执行报错,并提示,home_page(),需要传入参数,错误先放一放,待会修改

TypeError: home_page() takes 0 positional arguments but 1 was given

”单元测试/编写代码”循环

现在开始适应TDD 中的单元测试/ 编写代码循环了
(1) 在终端里运行单元测试,看它们是如何失败的。
(2) 在编辑器中改动最少量的代码,让当前失败的测试通过。

下面我们一次只修改一点的代码,然后运行测试,观察测试结果
打开并编辑lists/views.py,这个用例是定义home_page请求

def home_page(request):
pass

保存后, superlists目录下执行 python manage.py test,报错:”NoneType对象“没有”conten“属性

继续编辑lists/views,修改代码。执行 python manage.py test报错:self.assertTrue(html.startswith(’’))结果为假,不为真

from django.http import HttpResponsedef home_page(request):return HttpResponse()


继续编辑lists/views,修改代码,响应添加 '<html>' 。执行 python manage.py test报错:AssertionError: ‘To-Do lists’ not found in ‘’

from django.http import HttpResponsedef home_page(request):return HttpResponse('<html>')


继续编辑lists/views,修改代码, 响应添加<html><title>To-Do lists</title>。执行 python manage.py test报错:AssertionError: False is not true

from django.http import HttpResponsedef home_page(request):return HttpResponse('<html><title>To-Do lists</title>')


继续编辑lists/views,修改代码, 响应添加<html><title>To-Do lists</title></html>。执行 python manage.py test报错:AssertionError: False is not true

from django.http import HttpResponsedef home_page(request):return HttpResponse('<html><title>To-Do lists</title></html>')

执行通过,并返回OK

执行通过,我们根据用户story设计测试Case,然后一遍执行Case,一遍修改应用代码,那里错误就修改那里
直到测试用例完全通过,应用代码完成

单元测试通过后,我们进行功能测试,运行python functional_tests.py,功能测试执行通过,断言报错:AssertionError: Finish the test!

这个断言报错,注释掉那行代码就可以了,作者这里是故意这样写的,先保留

现在我们查看功能测试代码,测试用例完成了两个功能
第一个是用Selenium get()方法,调用浏览器,请求本地网址
第二个是打开新的网址,这个网址标题页名叫”To-Do“
做完之后,我们开始编写单元测试用例,根据单元测试,利用Django框架来编写应用代码

from selenium import webdriver
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)# self.fail('Finish the test!')# 应用邀请她输入一个待办事项if __name__ == '__main__':unittest.main(warnings='ignore')

当打开网页时,可以看到网页上的”To-Do lists“。至此,我们完成了一个用户story

然后查看状态,并且进行提交,提交完成后,我们可以用git log --oneline命令查看提交的历史记录和备注

$ git diff # 会显示tests.py中的新测试方法,以及views.py中的视图
$ git commit -am "Basic view now returns minimal HTML"
$ git log --oneline # 回顾提交历史备注

本章介绍了以下知识
• 新建Django 应用
• Django 的单元测试运行程序
• 功能测试和单元测试之间的区别
• Django 解析URL 的方法,urls.py 文件的作用
• Django 的视图函数,请求和响应对象
• 如何返回简单的HTML

Python 测试驱动开发读书笔记(三)使用单元测试测试简单的首页相关推荐

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

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

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

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

  3. Python 测试驱动开发读书笔记(二)使用unittest框架扩展功能测试

    使用功能测试驱动开发一个最简可用的应用 上一节,我们启用了Django,并且用Selenium驱动浏览器,测试了本地地址http://127.0.0.1,因为只是启动了首页,并没有做任何的页面内容,所 ...

  4. python cookbook是什么意思_《Python cookbook》读书笔记三

    itertools.dropwhile() 函数.使用时,你给它传递一个函数对象和一个可迭代对象.它会返回一个迭代器对象,丢弃原有序列中直到函数返回True 之前的所有元素,然后返回后面所有元素. i ...

  5. 第三章 使用单元测试测试简单的首页

    3.1第一个Django应用,第一个单元测试 python3 manage.py startapp lists 创建一个应用 功能测试站在用户的角度从外部测试应用,而单元测试从程序员的角度从内部测试应 ...

  6. 使用单元测试测试简单的首页

    Django鼓励以应用的形式组织代码.这样一个项目中可以放多个应用 为待办事项清单创建第一个应用: manage.py startapp Lists 会在superList文件夹中创建子文件夹List ...

  7. python第三章上机实践_《机器学习Python实践》读书笔记-第三章

    <机器学习Python实践>,第三章,第一个机器学习项目 以往目录:橘猫吃不胖:<机器学习Python实践>读书笔记-第一章​zhuanlan.zhihu.com 书中介绍了一 ...

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

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

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

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

最新文章

  1. 倪光南院士:openEuler与全球开发者共同推动计算产业发展
  2. php先分组后排序,PHP数组分组排序实例代码
  3. C++通过引用来传递和返回类对象
  4. C#项目单步调试莫名结束问题
  5. 保护SQL Server数据库的十大绝招
  6. Struts2原理图
  7. 不规则多边形填充_花一分钟看一个案例,PPT中图片填充形状的应用
  8. 【Flink】Flink Heartbeat of TaskManager with id timed out.
  9. 2021 年高教社杯全国大学生数学建模竞赛题目(A 题 “FAST”主动反射面的形状调节)
  10. Linux 开发环境工具 下载网址大全
  11. ffmpeg 安装bzlib_编译安装ffmpeg 要支持xvid、x264、mp3、ogg、amr、faac
  12. 抖音上python有用吗_专栏 | 如何在抖音上找到漂亮小姐姐?这里有个Python抖音机器人...
  13. 【计算机毕业设计】基于SpringBoot的物流管理系统
  14. word文档怎么压缩,word文档压缩方法
  15. 技术指南 | 理解零知识证明算法之Zk-stark
  16. 在和弦上进行旋律创作(不断更新)
  17. 【瓦片地图】瓦片地图坐标转换
  18. 安卓tv html,Emby for Android TV
  19. 使用EasyPoi导出Word文件,使用@Excel注释导出实体对象图片的解决方案
  20. 关于使用quartz动态增删改定时任务

热门文章

  1. Java中对象的比较 == 和 equals()
  2. 【单片机基础篇】三极管
  3. PCB画板子常用快捷键总结
  4. mysql 主从复制 表结构_MySQL主从复制-双主结构
  5. docker php安装gd扩展_给docker里的php安装gd扩展
  6. python pandas excel 排序_Pandas 按组汇总和列排序 - python
  7. cba篮球暂停次数和时间_为什么足球赛的观赏性比篮球更强?这三点是主要原因...
  8. mysql中的found_MySQL 中的 FOUND_ROWS() 与 ROW_COUNT() 函数
  9. 电子科技大学20春《c语言》在线作业1,电子科技大学20春《C语言(专科)》在线作业1.doc...
  10. java gc 可达性_JAVA--GC 垃圾回收机制----可达性分析算法