【有书共读15】Python测试驱动开发 读书笔记08

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

看一下 lists/tests.py 中的单元测试。现在,要查找特定的 HTML 字符串,但这不是测试HTML 的高效方法。一般来说,单元测试的规则之一是“不测试常量”。以文本形式测试HTML 很大程度上就是测试常量。
换句话说,如果有如下的代码:
wibble = 3
在测试中就不太有必要这么写:
from myprogram import wibble assert wibble == 3
单元测试要测试的其实是逻辑、流程控制和配置。编写断言检测 HTML 字符串中是否有指定的字符序列,不是单元测试应该做的。
而且,在 Python 代码中插入原始字符串真的不是处理 HTML 的正确方式。我们有更好的方法,那就是使用模板。如果把 HTML 放在一个扩展名为 .html 的文件中,
先不说其他好处,单就得到更好的句法高亮支持这一点而言也值了。Python 领域有很多模板框架,Django 有自己的模板系统,而且很好用。来使用这个模板系统吧。

使用模板重构


现在要做的是让视图函数返回完全一样的 HTML,但使用不同的处理方式。这个过程叫作重构,即在功能不变的前提下改进代码。
功能不变是最重要的。如果重构时添加了新功能,很可能会产生问题。重构本身也是一门学问,有专门的参考书——Martin Fowler 写的《重构》(http://refactoring.com/)。
重构的首要原则是不能没有测试。幸好我们在做测试驱动开发,测试已经有了。检查一下测试能否通过,测试能通过才能保证重构前后的表现一直:
$ python3 manage.py test
[...] OK
很好!先把 HTML 字符串提取出来写入单独的文件。新建用于保存模板的文件夹 lists/
templates,然后新建文件 lists/templates/home.html,再把 HTML 写入这个文件 2。
     lists/templates/home.html
<html>
<title>To-Do lists</title>
</html>
高亮显示的句法,漂亮多了!接下来修改视图函数:

from django.shortcuts import render
def home_page(request):
return render(request, 'home.html')
现在不自己构建 HttpResponse 对象了,转而使用 Django 中的 render 函数。这个函数的第一个参数是请求对象的,第二个参数是渲染的模板名。Django 会自动在所有的应用目录中搜索名为 templates 的文件夹,然后根据模板中的内容构建一个 HttpResponse 对象。
模板是 Django 中一个很强大的功能, 使用模板的主要优势之一是能把
Python 变量代入 HTML 文本。现在还没用到这个功能,不过后面的章节会用到。这就是为什么使用 render 和 render_to_string(稍后用到),而不用原生的 open 函数手动从硬盘中读取模板文件的缘故。
看一下模板是否起作用了:
$ python3 manage.py test
[...]
======================================================================
ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)➊
---------------------------------------------------------------------
Traceback (most recent call last):
File "/workspace/superlists/lists/tests.py", line 17, in test_home_page_returns_correct_html
response = home_page(request)➋
File "/workspace/superlists/lists/views.py", line 5, in home_page return render(request, 'home.html')➌
File "/usr/local/lib/python3.3/dist-packages/django/shortcuts.py", line 48, in render
return HttpResponse(loader.render_to_string(*args, **kwargs),
File "/usr/local/lib/python3.3/dist-packages/django/template/loader.py", line 170, in render_to_string
t = get_template(template_name, dirs)
File "/usr/local/lib/python3.3/dist-packages/django/template/loader.py", line 144, in get_template
template, origin = find_template(template_name, dirs)
File "/usr/local/lib/python3.3/dist-packages/django/template/loader.py", line 136, in find_template
raise TemplateDoesNotExist(name) django.template.base.TemplateDoesNotExist: home.html➍
---------------------------------------------------------------------
Ran 2 tests in 0.004s
又遇到一次分析调用跟踪的机会。
➊ 然后确认是哪个测试失败:很显然是测试视图 HTML 的测试。
➋ 然后找到导致失败的是测试中的哪一行:调用 home_page 函数那行。
➌ 最后,在应用的代码中找到导致失败的部分:调用 render 函数那段。
➍ 先看错误是什么:测试无法找到模板。
那为什么 Django 找不到模板呢?模板在 lists/templates 文件夹中,它就该放在这个位置啊。
原因是还没有正式在 Django 中注册 lists 应用。执行 startapp 命令以及在项目文件夹中存放一个应用还不够,你要告诉 Django 确实要开发一个应用,并把这个应用添加到文件
settings.py 中。这么做才能保证万无一失。打开 settings.py,找到变量 INSTALLED_APPS,把
lists 加进去:
     superlists/settings.py
# Application definition
INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'lists',
)
可以看出,默认已经有很多应用了。只需把 lists 加到列表的末尾。别忘了在行尾加上逗号,这么做虽然不是必须的,但如果忘了,Python 会把不在同一行的两个字符串连起来, 到时你就傻眼了。
现在可以再运行测试看看:
$ python3 manage.py test
[...]
self.assertTrue(response.content.endswith(b'</html>'))
AssertionError: False is not true
糟糕,还是无法通过。
你能否看到这个错误取决于你使用的文本编辑器是否会在文件的最后添加一个空行。如果没看到,你可以跳过下面几段,直接跳到测试通过那部分。
不过确实有进展。看起来测试找到模板了,但最后三个断言失败了。很显然输出的末尾出了问题。我使用 print repr(response.content) 调试这个问题,发现是因为转用模板后在响应的末尾引入了一个额外的空行(\n)。按下面的方式修改可以让测试通过:

self.assertTrue(response.content.strip().endswith(b'</html>'))

这么做有点像作弊,不过 HTML 文件末尾的空白并不重要。再运行测试看看:
$ python3 manage.py test
[...] OK

对代码的重构结束了,测试也证实了重构前后的表现一致。现在可以修改测试,不再测试常量,检查是否渲染了正确的模板。Django 中的另一个辅助函数 render_to_string 可以给些帮助:

from django.template.loader import render_to_string [...]

def test_home_page_returns_correct_html(self): request = HttpRequest()
response = home_page(request)
expected_html = render_to_string('home.html') self.assertEqual(response.content.decode(), expected_html)
使用 .decode() 把 response.content 中的字节转换成 Python 中的 Unicode 字符串,这样就可以对比字符串,而不用像之前那样对比字节。
这次重构想表达的重点是,不要测试常量,应该测试实现的方式。做得好!

Django 提供了一个测试客户端,其中有用于测试模板的工具。后面的章节会用到这个测试客户端。现在使用的是低层工具,目的是让你弄明白其中的工作方式。可以看出,并不神奇。







#Python##测试##笔记##读书笔记#
全部评论

相关推荐

1.&nbsp;事件概述3月10日下午,华为在“心声社区”发布长达6500字通报,曝光72名正式员工及19名非雇员在非雇员招聘中存在徇私舞弊行为,多人出卖公司信息资产获利,引发热议。-&nbsp;“非雇员”一般指华为OD员工,与人力服务公司签劳动合同,以派遣方式到华为工作,薪资待遇与华为内部员工基本一致,可通过考核转正。2.&nbsp;相关传言与真相华为相关人士称暂无官方回应,很多传言细节不准确。&nbsp;华为成都研究所员工透露,此次通报主要涉及成都研究所的数据存储部门,整个数据存储业务约100余人,此次明文通报除名辞退或通报批评的有62名,“很多部门基本全开除”&nbsp;。网传任正非亲赴成都、封楼抓人等消息不实。早在2024年年中,就有...
七安有出处嘛:省流:任正非亲赴成都等消息不实,2024 年年中就有人举报了;涉及36名违规当事人,其中有13人被除名;10人有主动申报情节或情节较严重的,予以辞退处理;另有13人被劝退、个人职级降3等。另外还有26名相关管理责任人作为直接或间接管理者,被处以个人职级降6等,冻结个人涨薪、职级晋升、干部向上任命,冻结期6—12个月不等;若下属违规偶发,则仅通报批评。并没有释放100HC😂😂😂
点赞 评论 收藏
分享
野猪不是猪🐗:好歹也统一一下吧,第一个项目技术栈用加号连接,第二个又变成逗号了
点赞 评论 收藏
分享
03-15 14:55
已编辑
门头沟学院 golang
bg:双非学院本&nbsp;ACM银&nbsp;go选手timeline:3.1号开始暑期投递3.7号第二家公司离职顽岩科技&nbsp;ai服务中台方向&nbsp;笔试➕两轮面试,二面挂(钱真的好多😭)厦门纳克希科技&nbsp;搞AI的,一面OC猎豹移动&nbsp;搞AIGC方向&nbsp;一面OC北京七牛云&nbsp;搞AI接口方向&nbsp;一面OC上海古德猫宁&nbsp;搞AIGC方向&nbsp;二面OC上海简文&nbsp;面试撞了直接拒深圳图灵&nbsp;搞AIGC方向一面后无消息懒得问了,面试官当场反馈不错其他小厂没记,通过率80%,小厂杀手😂北京字节&nbsp;具体业务不方便透露也是AIGC后端方向2.28约面&nbsp;(不知道怎么捞的我,我也没在别的地方投过字节简历哇)3.6一面&nbsp;一小时&nbsp;半小时拷打简历(主要是AIGC部分)剩余半小时两个看代码猜结果(经典go问题)➕合并二叉树(秒a,但是造case造了10分钟哈哈)一天后约二面3.12&nbsp;二面,让我挑简历上两个亮点说,主要说的docker容器生命周期管理和raft协议使用二分法优化新任leader上任后与follower同步时间。跟面试官有共鸣,面试官还问我docker底层cpu隔离原理和是否知道虚拟显存。之后一道easy算法,(o1空间解决&nbsp;给定字符串含有{和}是否合法)秒a,之后进阶版如何用10台机加快构建,想五分钟后a出来。面试官以为45分钟面试时间,留了18分钟让我跟他随便聊,后面考了linux&nbsp;top和free的部分数据说什么意思(专业对口了只能说,但是当时没答很好)。因为当时手里有7牛云offer,跟面试官说能否快点面试,马上另外一家时间到了。10分钟后约hr面3.13,上午hr面,下午走完流程offer到手3.14腾讯技术运营约面,想直接拒😂感受:&nbsp;因为有AIGC经验所以特别受AI初创公司青睐,AIGC后端感觉竞争很小(指今年),全是简历拷打,基本没有人问我八股(八股吟唱被打断.jpeg),学的东西比较广的同时也能纵向深挖学习,也运气比较好了哈哈可能出于性格原因,没有走主流Java路线,也没有去主动跟着课写项目,项目都是自己研究和写的哈哈
烤点老白薯:你根本不是典型学院本的那种人,贵了你这能力
查看5道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务