7. 测试代码
7.1 测试函数
要学习测试,就必须要有要测试的代码。
下面是一个简单的函数,他接受名和姓并返回整洁的姓名,示例:
def get_formatted_name(first, last):
'''生成整洁的姓名'''
full_name = f"{first} {last}"
return full_name.title()
为了核实函数是否能和期望那样来工作,再编写一个使用该函数的程序:
from zlx2 import get_formatted_name
print("Enter 'q' at any time quit.")
while True:
first = input("Please 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(formatted_name)
7.1.1 单元测试和测试用例
Python标准库中的unittest 提供了代码测试工具。单元测试用于核实该函数的某个方面没有问题。测试用例是一组单元测试,他们一道核实函数在各种情况下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖的测试用例包含一整套单元测试,覆盖率各种可能的函数的使用方式。对于大型项目,要进行全覆盖测试可能很难。通常,最初要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。
7.1.2 可通过的测试
我们需要一段时间才能习惯创建测试用例的语法,但创建测试用例后,在添加针对函数的单元测试就简单了。要为函数编写测试用例,可先导入模块unittest 和要测试的函数, 再创建一个继承unittest.TestCase 的类, 并编写一系列方法对函数的行为和不同方面进行测试。
示例:
import unittest
from zlx2 import get_formatted_name
class NamesTestCase(unittest.TestCase):
'''测试函数文件'''
def test_first_last_name(self):
'''能够正确地处理像zhou luxun这样的名字吗'''
formatted_name = get_formatted_name('zhou', 'luxun')
self.assertEquals(formatted_name, 'Zhou Luxun')
'''
使用了unittest 类中最有用的功能之一:断言方法,
断言方法核实得到的结果是否也期望的结果一致。
'''
if __name__ == '__main__':
unittest.main()
运行程序时,得到以下输出时表明测试通过了:
第一行的句点表明有一个测试通过了。接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK表明该测试用例中的所有单元测试都通过了。
7.1.3 未通过的测试
示例:
第一行的E,指出测试用例中有一个单元测试导致了错误。后面黄方框里面的都是错误提示。
7.1.4 测试未通过时怎么办
测试未通过时怎么办能?如果你检查的条件没错,测试通过意味着函数的行为是对的,而测试未通过意味着编写的新代码有错。因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚刚对函数所做的修改,找出导致函数行为不符合预期的修改。
示例:
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()
7.1.5 添加新测试
确定函数又能够正确处理简单的姓名后,我们再编写一个测试,用于包含中间名的姓名。为此,在类中再添加一个方法。
示例:
import unittest
from zlx2 import get_formatted_name
class NamesTestCase(unittest.TestCase):
'''测试函数文件'''
def test_first_last_name(self):
'''能够正确地处理像zhou luxun这样的名字吗'''
formatted_name = get_formatted_name('zhou', 'luxun')
self.assertEquals(formatted_name, 'Zhou Luxun')
'''
使用了unittest 类中最有用的功能之一:断言方法,
断言方法核实得到的结果是否也期望的结果一致。
'''
def test_first_last_modlle_name(self):
'''能够正确地处理像Zhou LU Xun这样的姓名吗'''
formatted_name = get_formatted_name('zhou', 'lu', 'xun')
self.assertEqual(formatted_name, 'Zhou Lu Xun')
if __name__ == '__main__':
unittest.main()
7.2 测试类
7.2.1 各种断言方法
unittest 模块中的断言方法:
方法 | 用途 |
---|---|
assertEqual(a, b) | 核实 a == b |
assertEqual(a, b) | 核实 a != b |
assertEqual(x) | 核实 x 为 Ture |
assertEqual(x) | 核实 x 为 False |
assertEqual(item, list) | 核实 item 在 list 中 |
assertEqual(item, list) | 核实 item 不在 list 中 |
7.2.2 一个要测试的类
类的测试与函数的测试相似,我们所做的大部分工作是测试类中发方法的行为。不过还是存在一些不同之处,下面来编写一个要测试的类。
示例:
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 zlx3 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)
my_survey.show_results()
7.2.3 测试这个AnonymonusSurvey 类
下面来编写一个测试, 对类的行为的一个方面进行验证:如果用户面对调查问题只提供一个答案,这个问题也能被妥善地存储。为此,我们将在这个答案被存储后,使用方法assertIn()来核实他确实在答案列表中。
示例:
import unittest
from zlx3 import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
def test_store_single_response(self):
question = "What language did you first learn to speak?"
my_surbey = AnonymousSurvey(question)
my_surbey.store_response('English')
self.assertIn('English', my_surbey.responses)
def test_store_three_responses(self):
question = "What language did you first learn to speak?"
my_surbey = AnonymousSurvey(question)
responses = ['English', '中文', 'Mandarin']
for response in responses:
my_surbey.store_response(response)
for response in responses:
self.assertIn(response, my_surbey.responses)
if __name__ == '__main__':
unittest.main()
7.2.4 方法 setUp()
在前面的例子中,我们在每个测试方法中都创建了一个AnonymousSurvey 实例, 并在每个方法中都创建了答案。unittest.TestCase 类包含的方法setUp() 让我们只需创建这些对象一次,就能在每个测试方法中使用。如果在TestCase 类中包含了方法setUp(),Python将先运行它,在运行各个以test_ 打头的方法。这样,在你编写的每个测试方法中,都可使用在方法setUp() 中创建的对象。
下面使用setUp()来创建一个调查对象和一组答案,供方法test_store_single_response()和test_store_three_responses()使用:
import unittest
from zlx3 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', '中文', '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()
注意: 运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点,测试引发错误时打印一个E,而测试导致断言失败时打印一个F。这就是我们运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试, 需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。