11.1.5 添加新测试
确定get_formatted_name()又能正确地处理简单的姓名后,我们再编写一个测试,用于测试 包含中间名的姓名。为此,我们在NamesTestCase类中再添加一个方法:
import unittest from name_function import get_formatted_name class 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这样的姓名吗?""" 1 formatted_name = get_formatted_name( 'wolfgang', 'mozart', 'amadeus') self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart') unittest.main()
我们将这个方法命名为test_first_last_middle_name()。方法名必须以test_打头,这样它才 会在我们运行test_name_function.py时自动运行。这个方法名清楚地指出了它测试的是get_ formatted_name()的哪个行为,这样,如果该测试未通过,我们就会马上知道受影响的是哪种类 型的姓名。在TestCase类中使用很长的方法名是可以的;这些方法的名称必须是描述性的,这才 能让你明白测试未通过时的输出;这些方法由Python自动调用,你根本不用编写调用它们的代码。
为测试函数get_formatted_name(),我们使用名、姓和中间名调用它(见1),再使用 assertEqual()检查返回的姓名是否与预期的姓名(名、中间名和姓)一致。我们再次运行 test_name_function.py时,两个测试都通过了:
.. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
太好了!现在我们知道,这个函数又能正确地处理像Janis Joplin这样的姓名了,我们还深信 它也能够正确地处理像Wolfgang Amadeus Mozart这样的姓名。
11.2 测试类
在本章前半部分,你编写了针对单个函数的测试,下面来编写针对类的测试。很多程序中都 会用到类,因此能够证明你的类能够正确地工作会大有裨益。如果针对类的测试通过了,你就能 确信对类所做的改进没有意外地破坏其原有的行为。
11.2.1 各种断言方法
Python在unittest.TestCase类中提供了很多断言方法。前面说过,断言方法检查你认为应 该满足的条件是否确实满足。如果该条件确实满足,你对程序行为的假设就得到了确认,你就可 以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,Python将引发异常。 表11-1描述了6个常用的断言方法。使用这些方法可核实返回的值等于或不等于预期的值、 返回的值为True或False、返回的值在列表中或不在列表中。你只能在继承unittest.TestCase的 类中使用这些方法,下面来看看如何在测试类时使用其中的一个。
unittestModule中的断言方法:
11.2.2 一个要测试的类
类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些 不同之处,下面来编写一个类进行测试。来看一个帮助管理匿名调查的类:
survey.py
class AnonymousSurvey(): """收集匿名调查问卷的答案""" 1 def __init__(self, question): """存储一个问题,并为存储答案做准备""" self.question = question self.responses = [] 2 def show_question(self): """显示调查问卷""" print(question) 3 def store_response(self, new_response): """存储单份调查答卷""" self.responses.append(new_response) 4 def show_results(self): """显示收集到的所有答卷""" print("Survey results:") for response in responses: print('- ' + response)
这个类首先存储了一个你指定的调查问题(见1),并创建了一个空列表,用于存储答案。 这个类包含打印调查问题的方法(见2)、在答案列表中添加新答案的方法(见3)以及将存储 在列表中的答案都打印出来的方法(见4)。要创建这个类的实例,只需提供一个问题即可。有 了表示调查的实例后,就可使用show_question()来显示其中的问题,可使用store_response()来 存储答案,并使用show_results()来显示调查结果。 为证明AnonymousSurvey类能够正确地工作,我们来编写一个使用它的程序:
language_survey.py
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': break my_survey.store_response(response) # 显示调查结果 print("\nThank you to everyone who participated in the survey!") my_survey.show_results()
这个程序定义了一个问题("What language did you first learn to speak? "),并使用这个 问题创建了一个AnonymousSurvey对象。接下来,这个程序调用show_question()来显示问题,并 提示用户输入答案。收到每个答案的同时将其存储起来。用户输入所有答案(输入q要求退出) 后,调用show_results()来打印调查结果:
What language did you first learn to speak? Enter 'q' at any time to quit. Language: English Language: Spanish Language: English Language: Mandarin Language: q Thank you to everyone who participated in the survey! Survey results: - English - Spanish - English
- Mandarin
AnonymousSurvey类可用于进行简单的匿名调查。假设我们将它放在了模块survey中,并想进 行改进:让每位用户都可输入多个答案;编写一个方法,它只列出不同的答案,并指出每个答案 出现了多少次;再编写一个类,用于管理非匿名调查。 进行上述修改存在风险,可能会影响AnonymousSurvey类的当前行为。例如,允许每位用户输 入多个答案时,可能不小心修改了处理单个答案的方式。要确认在开发这个模块时没有破坏既有 行为,可以编写针对这个类的测试。
11.2.3 测试 AnonymousSurvey 类
下面来编写一个测试,对AnonymousSurvey类的行为的一个方面进行验证:如果用户面对调查 问题时只提供了一个答案,这个答案也能被妥善地存储。为此,我们将在这个答案被存储后,使 用方法assertIn()来核实它包含在答案列表中:
test_survey.py
import unittest from survey import AnonymousSurvey 1 class TestAnonmyousSurvey(unittest.TestCase): """针对AnonymousSurvey类的测试""" 2 def test_store_single_response(self): """测试单个答案会被妥善地存储""" question = "What language did you first learn to speak?" 3 my_survey = AnonymousSurvey(question) my_survey.store_response('English') 4 self.assertIn('English', my_survey.responses) unittest.main()
我们首先导入了模块unittest以及要测试的类AnonymousSurvey。我们将测试用例命名为 TestAnonymousSurvey,它也继承了unittest.TestCase(见1)。第一个测试方法验证调查问题的 单个答案被存储后,会包含在调查结果列表中。对于这个方法,一个不错的描述性名称是 test_store_single_response()(见2)。如果这个测试未通过,我们就能通过输出中的方法名得 知,在存储单个调查答案方面存在问题。
要测试类的行为,需要创建其实例。在3处,我们使用问题"What language did you first learn to speak?"创建了一个名为my_survey的实例,然后使用方法store_response()存储了单个答案 English。接下来,我们检查English是否包含在列表my_survey.responses中,以核实这个答案是 否被妥善地存储了(见4)。 当我们运行test_survey.py时,测试通过了:
. ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
这很好,但只能收集一个答案的调查用途不大。下面来核实用户提供三个答案时,它们也将 被妥善地存储。为此,我们在TestAnonymousSurvey中再添加一个方法:
import unittest from survey import AnonymousSurvey class 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) 1 responses = ['English', 'Spanish', 'Mandarin'] for response in responses: my_survey.store_response(response) 2 for response in responses: self.assertIn(response, my_survey.responses) unittest.main()
我们将这个方法命名为test_store_three_responses(),并像test_store_single_response() 一样,在其中创建一个调查对象。我们定义了一个包含三个不同答案的列表(见1),再对其中 每个答案都调用store_response()。存储这些答案后,我们使用一个循环来确认每个答案都包含 在my_survey.responses中(见2)。
我们再次运行test_survey.py时,两个测试(针对单个答案的测试和针对三个答案的测试)都 通过了:
.. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK
前述做法的效果很好,但这些测试有些重复的地方。下面使用unittest的另一项功能来提高 它们的效率。