文章目录

  • 前言
  • 1 测试函数
    • 1.1 单元测试和测试用例
    • 1.2 可通过的测试
    • 1.3 不能通过的测试
    • 1.4 测试未通过时怎么办
    • 1.5 添加新测试
  • 2 测试类
    • 2.1 各种断言方法
    • 2.2 一个要测试的类
    • 2.3 测试 AnonymousSurvey 类
    • 2.4 方法 setUp()
    • 动手试一试11-3

前言

Python初学者一枚,文章仅为个人学习记录,便于以后查看使用。

编写函数或类时,还可为其编写测试。通过测试,可确定代码面对各种输入都能够按要求的那样工作。在程序中添加新代码时,你也可以对其进行测试,确认它们不会破坏程序既有的行为。

1 测试函数

name_function.py

def get_formatted_name(first, last):"""Generate a neatly formatted full name"""full_name = first + ' ' + lastreturn full_name.title()

1.1 单元测试和测试用例

Python标准库中的模块unittest提供了代码测试工具。

单元测试用于核实函数的某个方面没有问题;
测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。

良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。

1.2 可通过的测试

要为函数编写测试用例,可先导入模块unittest以及要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。

下面是一个只包含一个方法的测试用例,它检查函数get_formatted_name()在给定名和姓时能否正确地工作:

test_name_ function.py

import unittest
from name_function import get_formatted_nameclass NamesTestCase(unittest.TestCase):"""测试name_function.py"""def test_first_last_name(self):"""能够正确地处理像Janis Joplin这样的姓名吗?"""formatted_name = get_formatted_name('janis', 'joplin')self.assertEqual(formatted_name, 'Janis Joplin')# 第一种
main = NamesTestCase
# main等于创建的类的名称,即class后的名称
'''
第二种:unittest.main(argv=['first-arg-is-ignored'], exit=False)
第三种:unittest.main(argv=['ignored', '-v'], exit=False)
'''

▲▲▲最后行书中原文写unittest.main(),但会报错:

解决方法及原因第一页最后一条回复(2020-02-07 16:51:13)、第二页最后一条回复(2021-02-01 10:58:49)亲测有效。

首先,我们导入了模块unittest和要测试的函数get_formatted_ name()。
在第4行,我们创建了一个名为NamesTestCase的类,用于包含一系列针对get_formatted_name()的单元测试。你可随便给这个类命名,但最好让它看起来与要测试的函数相关,并包含字样Test这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试。
NamesTestCase只包含一个方法,用于测试get_formatted_name()的一个方面。我们将这个方法命名为test_first_last_name(),因为我们要核实的是只有名和姓的姓名能否被正确地格式化。
我们运行test_name_function.py时,所有以test_打头的方法都将自动运行。在这个方法中,我们调用了要测试的函数,并存储了要测试的返回值。在这个示例中,我们使用实参’janis’和’joplin’调用get_formatted_name(),并将结果存储到变量formatted_name中(见第9行)。
在第10行,我们使用了unittest类最有用的功能之一:一个断言方法。断言方法用来核实得到的结果是否与期望的结果一致。
在这里,我们知道get_formatted_name()应返回这样的姓名,即名和姓的首字母为大写,且它们之间有一个空格,因此我们期望formatted_name的值为Janis Joplin。为检查是否确实如此,我们调用unittest的方法assertEqual(),并向它传递formatted_ name和’Janis Joplin’。
代码行self.assertEqual(formatted_name, ‘Janis Joplin’)的意思是说:“将formatted_name的值同字符串’Janis Joplin’进行比较,如果它们相等,就万事大吉,如果它们不相等,跟我说一声!”

代码行unittest.main()让Python运行这个文件中的测试。

书中运行test_name_function.py时,得到的输出:

1.3 不能通过的测试

新版本函数get_formatted_name(),它要求通过一个实参指定中间名:

name_function.py

def get_formatted_name(first, middle, last):"""Generate a neatly formatted full name"""full_name = first + ' ' + middle + ' ' + lastreturn full_name.title()

运行程序test_name_function.py时,输出如下:

最后一行按第一种方式

最后一行按第二种方式

书中原文:

1.4 测试未通过时怎么办

如果你检查的条件没错,测试通过了意味着函数的行为是对的,而测试未通过意味着你编写的新代码有错。因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚对函数所做的修改,找出导致函数行为不符合预期的修改。

修改name_function.py

def get_formatted_name(first, last, middle=''):"""Generate a neatly formatted full name"""if middle:full_name = first + ' ' + middle + ' ' + lastelse:full_name = first + ' ' + lastreturn full_name.title()

1.5 添加新测试

确定get_formatted_name()又能正确地处理简单的姓名后,我们再编写一个测试,用于测试包含中间名的姓名。为此,我们在NamesTestCase类中再添加一个方法:

import unittest
from name_function import get_formatted_nameclass NamesTestCase(unittest.TestCase):"""测试name_function.py"""def test_first_last_name(self):"""能够正确地处理像Janis Joplin这样的姓名吗?"""formatted_name = get_formatted_name('janis', 'joplin')self.assertEqual(formatted_name, 'Janis Joplin')def test_first_last_middle_name(self):"""能够正确地处理像Wolfgang Amadeus Mozart这样的姓名吗?"""formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')# 第一种
main = NamesTestCase
# main等于创建的类的名称,即class后的名称
'''
第二种:unittest.main(argv=['first-arg-is-ignored'], exit=False)
第三种:unittest.main(argv=['ignored', '-v'], exit=False)
'''

在TestCase类中使用很长的方法名是可以的;这些方法的名称必须是描述性的,这才能让你明白测试未通过时的输出;这些方法由Python自动调用,你根本不用编写调用它们的代码。

2 测试类

2.1 各种断言方法

Python在unittest.TestCase类中提供了很多断言方法。断言方法检查你认为应该满足的条件是否确实满足。如果该条件确实满足,你对程序行为的假设就得到了确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,Python将引发异常。

表11-1描述了6个常用的断言方法。使用这些方法可核实返回的值等于或不等于预期的值、返回的值为True或False、返回的值在列表中或不在列表中
你只能在继承unittest.TestCase的类中使用这些方法

2.2 一个要测试的类

类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处。

示例:

class AnonymousSurvey():"""收集匿名调查问卷的答案"""def __init__(self, question):"""存储一个问题,并为存储答案做准备"""self.question = questionself.responses = []def show_question(self):"""显示调查问卷"""print(self.question)def store_response(self, new_response):"""存储单份调查答卷"""self.responses.append(new_response)def show_results(self):"""显示收集到的所有答卷"""print("Survey results:")for response in self.responses:print('- ' + response)

这个类首先存储了一个你指定的调查问题(见第4行),并创建了一个空列表,用于存储答案。这个类包含打印调查问题的方法(见第9行)、在答案列表中添加新答案的方法(见第13行)以及将存储在列表中的答案都打印出来的方法(见第17行)。要创建这个类的实例,只需提供一个问题即可。有了表示调查的实例后,就可使用show_question()来显示其中的问题,可使用store_response()来存储答案,并使用show_results()来显示调查结果。

为证明AnonymousSurvey类能够正确地工作,我们来编写一个使用它的程序:

from survey import AnonymousSurvey# 定义一个问题,并创建一个表示调查的AnonymousSurvey对象
question = 'What language did you first learn to speak?'
my_survey = AnonymousSurvey(question)# 显示问题并存储答案
my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:response = input("Language: ")if response == 'q':breakmy_survey.store_response(response)# 显示调查结果
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()

输出:

书中survey.py中,第11行和第20行原文错误,为print(question)和for response in responses:

2.3 测试 AnonymousSurvey 类

下面来编写一个测试,对AnonymousSurvey类的行为的一个方面进行验证:如果用户面对调查问题时只提供了一个答案,这个答案也能被妥善地存储。为此,我们将在这个答案被存储后,使用方法assertIn()来核实它包含在答案列表中:

import unittest
from survey import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):"""针对AnonymousSurvey类的测试"""def test_store_single_response(self):"""测试单个答案会被妥善地存储"""question = "What language did you first learn to speak?"my_survey = AnonymousSurvey(question)my_survey.store_response('English')self.assertIn('English', my_survey.responses)main = TestAnonymousSurvey

我们首先导入了模块unittest以及要测试的类AnonymousSurvey。
我们将测试用例命名为TestAnonymousSurvey,它也继承了unittest.TestCase(见第4行)。第一个测试方法验证调查问题的单个答案被存储后,会包含在调查结果列表中。

要测试类的行为,需要创建其实例。在第10行,我们使用问题"What language did you first learn to speak?"创建了一个名为my_survey的实例,然后使用方法store_response()存储了单个答案English。

接下来,我们检查English是否包含在列表my_survey.responses中,以核实这个答案是否被妥善地存储了(见第13行)。

当我们运行test_survey.py时,测试通过了。

下面来核实用户提供三个答案时,它们也将被妥善地存储。为此,我们在TestAnonymousSurvey中再添加一个方法:

import unittest
from survey import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):"""针对AnonymousSurvey类的测试"""def test_store_single_response(self):"""测试单个答案会被妥善地存储"""--snip--def test_store_three_responses(self):"""测试三个答案会被妥善地存储"""question = "What language did you first learn to speak?"my_survey = AnonymousSurvey(question)responses = ['English', 'Spanish', 'Mandarin']for response in responses:my_survey.store_response(response)for response in responses:self.assertIn(response, my_survey.responses)main = TestAnonymousSurvey

前述做法的效果很好,但这些测试有些重复的地方。下面使用unittest的另一项功能来提高它们的效率。

2.4 方法 setUp()

在前面的test_survey.py中,我们在每个测试方法中都创建了一个AnonymousSurvey实例,并在每个方法中都创建了答案。

unittest.TestCase类包含方法setUp(),让我们只需创建这些对象一次,并在每个测试方法中使用它们。如果你在TestCase类中包含了方法setUp(),Python将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个测试方法中都可使用在方法setUp()中创建的对象了。

下面使用setUp()来创建一个调查对象和一组答案,供方法test_store_single_response()和test_store_three_responses()使用:

import unittest
from survey import AnonymousSurveyclass TestAnonymousSurvey(unittest.TestCase):"""针对AnonymousSurvey类的测试"""def setUp(self):"""创建一个调查对象和一组答案,供使用的测试方法使用"""question = "What language did you first learn to speak?"self.my_survey = AnonymousSurvey(question)self.responses = ['English', 'Spanish', 'Mandarin']def test_store_single_responses(self):self.my_survey.store_response(self.responses[0])self.assertIn(self.responses[0], self.my_survey.responses)def test_store_three_responses(self):"""测试三个答案会被妥善地存储"""for response in self.responses:self.my_survey.store_response(response)for response in self.responses:self.assertIn(response, self.my_survey.responses)main = TestAnonymousSurvey

方法setUp()做了两件事情:创建一个调查对象(见第11行);创建一个答案列表(见第12行)。存储这两样东西的变量名包含前缀self(即存储在属性中),因此可在这个类的任何地方使用。
方法test_store_three_response()核 实 self.responses 中的第一个答案 ——self.responses[0]—— 被妥善地存储,而方法test_store_three_response()核实self.responses中的全部三个答案都被妥善地存储。

再次运行test_survey.py时,这两个测试也都通过了。修改代码以接受多个答案后,可运行这些测试,确认存储单个答案或一系列答案的行为未受影响。

测试自己编写的类时,方法setUp()让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。

注意(看看即可):运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个E;测试导致断言失败时打印一个F。这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。

动手试一试11-3

雇员:编写一个名为 Employee 的类,其方法__init__()接受名、姓和年薪,并将它们都存储在属性中。编写一个名为 give_raise()的方法,它默认将年薪增加 5000美元,但也能够接受其他的年薪增加量。
为 Employee 编写一个测试用例,其中包含两个测试方法:test_give_default_ raise()和 test_give_custom_raise()。使用方法 setUp(),以免在每个测试方法中都创建新的雇员实例。运行这个测试用例,确认两个测试都通过了。

employee.py

class Employee():def __init__(self, first, last, salary):self.first = firstself.last = lastself.salary = salarydef give_raise(self, addsalary=5000):self.salary += addsalary

test_employee.py

import unittest
from employee import Employeeclass TestEmployee(unittest.TestCase):def setUp(self):self.formatted_default = Employee('Edward', 'Elric', 1000)def test_give_default_raise(self):self.formatted_default.give_raise()self.assertEqual(self.formatted_default.salary, 6000)def test_give_custom_raise(self):self.formatted_default.give_raise(10000)self.assertEqual(self.formatted_default.salary, 11000)main = TestEmployee

《Python编程:从入门到实践》学习笔记——第11章 测试代码相关推荐

  1. python基础学习[python编程从入门到实践读书笔记(连载一)]

    写在前面:本文来自笔者关于<python编程从入门到实践>的读书笔记与动手实践记录. 程序员之禅 文章目录 02变量和简单数据类型 03 列表简介 04 操作列表 05 if语句 06 字 ...

  2. Python编程从入门到实践(第三、四章的列表和元祖)

    1.Python中列表用[]来表示,并用逗号分隔其中元素 2.访问列表元素,给出元素的索引值即可(索引从0开始) 3.修改,添加和删除元素 3.1修改时给出列表名和修改元素的索引,然后赋新值 3.2在 ...

  3. python基础学习[python编程从入门到实践读书笔记(连载三)]:django学习笔记web项目

    文章目录 Django项目:学习笔记web网页 项目部署 参考 自己部署的网站,还是小有成就感的,毕竟踩过很多坑,实战技能也有些许进步. 网站链接:http://lishizheng.herokuap ...

  4. python基础学习[python编程从入门到实践读书笔记(连载五)]:数据可视化项目第16章

    文章目录 下载数据 制作全球地震散点图:JSON格式 end 几个实验结果: 每日最高气温: 地震图绘制: 下载数据 CSV文件格式 在文本文件中存储数据,一个简单方式是将数据作为一系列以逗号分隔的值 ...

  5. python基础学习[python编程从入门到实践读书笔记(连载四)]:数据可视化项目第15章

    文章目录 matplotlib初学 基础绘图用法 随机漫步 使用Plotly模拟掷骰子 matplotlib初学 基础绘图用法 import matplotlib.pyplot as pltsquar ...

  6. python编程从入门到实践读书笔记-《Python编程:从入门到实践》项目部分读书笔记(二)...

    鸽了一个暑假没有更新,现在趁着还没开学更一下.咕咕咕 上期作业: 请创建一个Django项目,命名为Blog,建不建立虚拟环境随便你,最后本地跑成了就行. 步骤: ①在需要创建工程的文件夹下打开cmd ...

  7. python基础学习[python编程从入门到实践读书笔记(连载二)]:外星人入侵项目

    第一版游戏demo 添加计分系统:中间是最高得分,右边是本次得分. 显示余下的飞船数 主函数如下,完整程序将上传到笔者的github:https://github.com/shizhengLi/lea ...

  8. python基础学习[python编程从入门到实践读书笔记(连载六)]:数据可视化项目第17章

    文章目录 使用API end 项目结果: 使用plotly可视化github最受欢迎的python仓库: 修改后的可视化图表: 使用API 编写独立的程序,实现对获取的数据可视化.我们使用Web AP ...

  9. python编程:从入门到实践 阅读笔记

    文章目录 第一章 起步 第二章 变量和简单数据类型 String 数字 整数 浮点数 第三章 列表简介 第四章 操作列表 元组 第五章 if 语句 第六章 字典 第七章 用户输入和while循环 第八 ...

最新文章

  1. HTTP头入门到精通(每一个HTTP消息头解释)
  2. 移动端自动播放音视频实现代码
  3. 7.1.15 单双击事件
  4. Spark2.2.0 分布式离线搭建
  5. leetcode 【 Unique Paths 】python 实现
  6. 诗与远方:无题(九)
  7. Linux进程里运行新代码,linux调度器源码分析 - 新进程加入(三)
  8. 自己动手写DB数据库框架(增)
  9. hread.interrupt()到底意味着什么
  10. getParameter和getAttribute区别(超详细分析)
  11. 2D渲染pixi项目实战总结
  12. ResultSet.TYPE_SCROLL_SENSITIVE到底发生了什么?
  13. 2、Ubuntu介绍加环境搭建详细教程
  14. 云计算 : 公有云、私有云、混合云到底是个啥玩意?
  15. 轻松解决 Eclipse Indigo 3.7 中文字体偏小,完美 Consolas 微软雅黑混合字体!
  16. matlab中离散信号模型
  17. DDL命令及基础知识
  18. 鸿蒙手机自动化测试,先睹为快!全球首款华为手机已经升级到鸿蒙了
  19. 云计算机平台的特性,云平台对比传统平台特点分析
  20. 性能监控的95分位值意义

热门文章

  1. 如何使用报表工具FastReport VCL 设计器中的 PDF/A?
  2. <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent
  3. 显示unc路径服务器根目录,IIS虚拟目录与UNC路径权限初探
  4. 水母缸的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  5. 星际、魔兽3、红警大PK!
  6. Spring Cloud 优雅下线+灰度发布
  7. windos无法在此计算机硬件上,解决:windows安装程序无法将windows配置为在此计算机的硬件上运行...
  8. C++11 recursive_mutex
  9. 手机游戏引擎 Cocos2d-x
  10. TCP/IP协议之应用层