介绍
在本章中你会学习如何使用Python模块nuittest中的工具来测试代码。你将了解测试通过和未通过的样子;同时,你将学习如何测试函数和类;和知道该为项目编写多少个测试。
测试函数
要学习测试,先得有测试的代码。下面编写了一个简单的函数,它接受名和姓并返回整洁的姓名。
name_function.py
def get_formatted_name(first,last):
full_name=first+' '+last
return full_name.title()
为核实get_formatted_name()像期望那样工作,我们来编写一个使用这个函数的程序。程序names.py让用户输入名和姓,并显示整洁的全名。
names.py
from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
while True:
first = input("please input your first name:")
if first == 'q':
break
last = input("please input your last name:")
if last == 'q':
break
formatted_name=get_formatted_name(first,last)
print("\tNeatly formatted name:"+formatted_name+'.')
单元测试和测试用例
python标注库中的模块nuittest提供了代码测试工具。
良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。
单元测试用于核实函数某个方面没有问题
测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。
全覆盖测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。
可通过的测试
要为函数编写测试用例,可先导入模块unittest以及要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。
下面是一个只包含一个方法的测试用例,它检查函数get_formatted_name()在给定名和姓时是否正确地工作。
test_name_function.py
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
def test_first_last_name(self):
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'janis joplin') #3
unittest.main()
解释:类nameTestCase继承了unittest.TestCase类。
3处我们使用了unittest类最有用的功能之一:一个断言方法,断言方法用来核实得到的结果是否与预期的结果一致。比较fomatted_name的值和字符串’janis joplin’,如果他们相等,就万事大吉,如果他们不相等就跟我说一声。
结果出现句点和OK,表示测试通过了。
不能通过的测试
在name_function.py中添加一个名为new_get_formatted_name(first,middle,last)的函数,添加一个中间名。再放到test_name_function.py中测试:self.assertEqual(formatted_name, ‘janis joplin’),测试代码只改变为新的函数,其余不变。
E ❶
======================================================================
ERROR: test_first_last_name (__main__.NamesTestCase) ❷
----------------------------------------------------------------------
Traceback (most recent call last): ❸
File "test_name_function.py", line 8, in test_first_last_name
formatted_name = get_formatted_name('janis', 'joplin')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
----------------------------------------------------------------------
Ran 1 test in 0.000s ❹
FAILED (errors=1) ❺
报错:
- 第一行只输出一个字母E,他指出测试用例中有一个单元测试导致了错误。
- ②处表示为:NamesTestCase中的test_first_last_name()导致了错误。测试用例中包含从多单元测试时,知道那个测试未通过至关重要。
- 在③处,它指出函数get_formatted_name(‘janis’,‘joplin’)有问题,因为它缺少一个必不可少的位置实参。
- 第④处,我们可以看到运行了一个单元测试。
- 第⑤处,我们可以知道测试该用例一共有几个错误。
测试未通过时怎么办
如果你检查的条件没有错,测试通过了意味着函数的行为是对的,而测试没有通过意味着你编写的新代码有错。因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码。
在这个示例中,get_formatted_name()以前只需要两个实参–姓和名,但现在它要求提供中间名。新增的中间名参数是必不可少的,这导致get_formatted_name()的行为不符合预期。故最佳选择为让中间名变为可选的。
name_function.py
def new_get_formatted_name(first, last, middle=''):
if middle:
full_name = first + ' ' + middle + ' ' + last
return full_name
else:
full_name = first + ' ' + last
return full_name
再进行测试,测试通过。
添加新测试
确定get_formatted_name()又能正确地处理简单的姓名后,我们再编写一个测试。用于测试包含中间名的姓名。
为此,我们在NamesTestCase类中再添加一个方法。
import unittest
from name_function import new_get_formatted_name
class NamesTestCase(unittest.TestCase):
def test_first_last_name(self):
formatted_name = new_get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'janis joplin')
def test_first_last_middle_name(self):
formatted_name = new_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时自动运行。
在TestCase类中,使用很长的方法名是可以的,这些方法名称必须是描述性的,这样才能让你明白测试未通过时的输出。
这些方法由python调用,不用你编写他们的代码。
测试类
各种断言方法
python在unittest.TestCase类中提供了很多断言的方法。前面说过,断言方法检查你认为应该满足的条件是否可以满足。如果该条件满足,你对程序的假设就得了确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际并不满足,python将引发异常。
常见的断言方法有六个:
方法 | 用途 |
---|---|
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 中 |
一个要测试的类
下面来编写一个类进行测试,创建一个帮助管理匿名调查的类。
survey.py
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('-'+response)
编写一个使用该类的程序
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
else:
my_survey.store_response(response)
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()
测试AnonymousSurvey类
编写一个测试,对AnonymousSurvey类的行为的一个方面进行验证:如果用户面对调查问题时只提供了一个答案,这个答案也能被妥善地存储。为此,我们将在这个答案被存储后,使用方法assertln()来核实它包含在答案列表中:
test_survey.py
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
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)
unittest.main()
测试成功,但它只能收集一个答案的调查用途不大。下面来核实用户提供三个答案时,他们也将被妥善地存储。故,我们在TestAnonymousSurvey中添加一个方法。
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
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)
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)
unittest.main()
方法setUp()
前面的测试效果很好,但有些测试有重复的地方,接下来使用unittest的另一项功能来提高它们的效率。
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):
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_response(self):
for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
unittest.main()
解释:方法setUp()做了两件事:创建一个调查对象;创建一个答案列表。存储这两样东西的变量名包含前缀self(即存储在属性中),因此可在这个类的任何地方使用。这让两个测试方法都更简单,因为它们都不用创建调查对象和答案。
方法setUp()让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。