目录
- 一、测试函数
- 2.1 通过案例
- 2.2 不通过案例
- 2.3 添加新测试
- 二、测试类
- 2.1 单个测试案例
- 2.2 多个测试案例
- 三、总结
遇到看不明白的地方,欢迎在评论中留言呐,一起讨论,一起进步!
本文参考:《Python编程:从入门到实践(第2版)》
一、测试函数
首先我们准备一个要测试的函数,这个函数在名和姓之间加上一个空格并将其首字母大写,再返回结果。
"""文件名:name_function"""
def get_formatted_name(first,last):
"""生成整洁的姓名。"""
full_name = f"{first} {last}"
return full_name.title()
我们再来编写一个使用该函数的程序:
from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
while True:
first = input("\nPlease give me a first name:")
if first == 'q':
break
last = input("Please give me a last name:")
if last == 'q':
break
formatted_name = get_formatted_name(first,last)
print(f"\tNeatly formatted name:{formatted_name}.")
运行结果如下:
Python 标准库中的模块 unittest 提供了代码测试工具。 单元测试用于核实函数的某个方面没有问题。这样一来我们就不用自己编写程序依次运行了。
测试用例是一组单元测试,它们用来核实函数在各种情形下的行为是否都符合要求。
要进行单元测试,可先导入模块 unittest 和要测试的函数,再创建一个继承 unittest.TestCase 的类,并编写一系列方法对函数行为的不同方面进行测试。
2.1 通过案例
下面的测试用例只包含一个方法,它检查函数 get_formatted_name()
在给定名和姓时能否正确工作:
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')
if __name__ == '__main__':
unittest.main()
在运行这段程序时,所有以 test_
打头的方法都将自动运行,这里 test_first_last_name
将自动执行。
在函数 test_first_last_name
中,使用了 unittest 类最有用的功能之一:断言方法,用来核实得到的结果是否与期望的结果一致。
这里我们将介绍 6 种常用地断言方法。
注:只能在继承 unittest.TestCas 的类中使用这些方法
方法 用途 assertEqual(a,b)
核实 a == b assertNotEqual(a,b)
核实 a != b assertTrue(x)
核实 x == True assertFalse(x)
核实 x == False assertIn(item,list)
核实 item 在 list 中 assertNotIn(item,list)
核实 item 不在 list 中
if
代码块检查特殊变量 __name__
,这个变量是在程序执行时设置的。
如果这个文件作为主程序执行,变量 __name__
将被设置为__main__,此时调用 unittest.main()
来运行测试用例。
如果这个文件被测试框架导入,变量 __name__
的值将不是__main__,则不会调用 unittest.main()
。
运行这段程序结果如下:
最后的 OK 表明该测试用例中的所有单元测试都通过了。
2.2 不通过案例
下面我们对被测试的函数进行修改,用于处理有中间名的格式:
def get_formatted_name(first,middle,last):
"""生成整洁的姓名。"""
full_name = f"{first} {middle} {last}"
return full_name.title()
我们仍然使用原来的测试用例,让它检查函数 get_formatted_name()
在给定名和姓时能否正确工作,最后运行结果如下:
这里我们可以清楚地看到发生了几处错误,错误具体在哪里发生。
要将中间名设置为可选的,可在函数定义中将形参 middle 移到形参列表末尾,并将其默认值指定为一个空字符串。还需要添加一个 if 测试,以便根据是否提供了中间名相应地创建姓名:
def get_formatted_name(first,last,middle=''):
"""生成整洁的姓名。"""
if middle:
full_name = f"{first} {middle} {last}"
else:
full_name = f"{first} {last}"
return full_name.title()
我们再来运行测试用例,最后运行结果如下:
现在,测试用例通过了。这意味着这个函数又能正确处理像 Janis Joplin 这样的姓名了,而且我们无须手工测试这个函数。 这个函数之所以很容易修复,是因为未通过的测试让我们得知新代码破坏了函数原来的行为。
2.3 添加新测试
下面我们再编写一个测试,用于测试包含中间名的姓名。为此,在 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这样的姓名吗?"""
formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
if __name__ == '__main__':
unittest.main()
注意:测试方法名必须以 test_ 打头,这样它才会在我们运行时自动运行。
再次运行一下,两个测试都通过了!
现在我们知道,这个函数又能正确地处理像 Janis Joplin 这样的姓名了,而且深信它也能够正确地处理像 Wolfgang Amadeus Mozart 这样的姓名。
二、测试类
类的测试与函数的测试相似,我们所做的大部分工作是测试类中方法的行为。不过还是存在一些不同之处,下面编写一个要测试的类:
class AnonymousSurvey:
"""收集匿名调查问卷的答案。"""
def __init__(self,question):
"""存储一个问题,并为存储答案做准备。"""
self.question = question
self.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(f"- {response}")
为证明 AnonymousSurvey
类能够正确工作,编写一个使用它的程序:
from survey import 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()
执行结果如下:
2.1 单个测试案例
下面来编写一个测试,对 AnonymousSurvey
类的行为的一个方面进行验证:如果用户面对调查问题只提供一个答案,这个答案也能被妥善地存储。为此,我们将在这个答案被存储后,使用方法 assertIn()
来核实它确实在答案列表中:
import unittest
from survey import AnonymousSurvey
class 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)
if __name__ == '__main__':
unittest.main()
注意:要测试类的行为,需要创建其实例。
运行上述代码,我们看到测试通过了:
2.2 多个测试案例
我们可以像测试函数一样定义多个以 “test_” 开头地函数来测试类的不同方面,这时我们需要在每个函数下都要对被测试的类进行实例化。
而 unittest.TestCase 类包含的方法 setUp()
让我们只需创建这些对象一次,就能在每个测试方法中使用:如果在 TestCase 类中包含了方法 setUp(),Python 将先运行它,再运行各个以 “test_” 打头的方法。这样,在我们编写的每个测试方法中,都可使用在方法 setUp() 中创建的对象。
下面使用 setUp()
来创建一个调查对象和一组答案,供方法 test_store_single_response()
和 test_store_three_responses()
使用:
import unittest
from survey import AnonymousSurvey
class 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_response(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)
if __name__ == '__main__':
unittest.main()
运行结果如下:
测试自己编写的类时,方法 setUp()
让测试方法编写起来更容易:可在 setUp()
方法中创建一系列实例并设置其属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。
三、总结
在本文中,我们学习了:如何使用模块 unittest 中的工具来为函数和类编写测试,如何编写继承 unittest.TestCase 的类,以及如何编写测试方法,以核实函数和类的行为符合预期;如何使用方法 setUp() 来根据类高效地创建实例并设置其属性,以便在类的所有测试方法中使用。