编写函数或类时,可为其编写测试。学习使用Python模块unittest
中的工具来测试代码。
测试代码
1测试函数
要学习测试,的要有测试的代码
name_function.py
:一个简单的函数,接受姓和名并返回整洁的名字。
def get_formatted_name(first, last):
"""生成格式工整的全名"""
full_name = first + ' ' + last
return full_name.title()
name.py
:让用户输入名和姓,并显示整洁的全名
from name_function import get_formatted_name
print("(enter 'q' at any time to quit)")
while True:
first = input("\nPlease tell me your first name:")
if first == 'q':
break
last = input("last_name")
if last == 'q':
break
full_name = get_formatted_name(first, last)
print(full_name)
现在假设要修改get_formatted_name()
使其能够处理中间名字。这样做时,需要考虑不破坏这个函数处理只有名和姓 的姓名方式。为此,可以在每次修改get_formatted_name()
时都测试:运行name.py
,并输入正确的姓名,但这种方式太繁琐。
为此Python提供了一种自动测试函数输出的高效方式。倘若对函数get_formatted_name()
进行自动测试,就能始终确信这个函数提供我们测试过的姓名时,它都能正确地运行。
1.1单元测试和单元用例
Python标准去中的模块unittest
提供了代码测试工具。
单元测试用于核实函数的某个方面没有问题。
测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。
全覆盖式测试用例包含一正太单元测试,涵盖了各种可能的函数使用方法。
1.2可通过的测试
要为函数编写测试用例,可先导入模块unittest
以及要测试的函数,再创建一个继承unit test.TestCase
的类,并编写一系列方法对函数行为的不同方面进行测试。
下面test_name_function.py
文件只包含一个方法的测试用例,它检测函数get_formatted_name()
在给定名和姓时能否能正确地工作。
import unittest
from name_function import get_formatted_name
class NameTestCase(unittest.TestCase): #1
"""测试name_function.py"""
def test_first_last_name(self):
"""能够正确地处理像zhang san这样的名字吗"""
formatted_name = get_formatted_name('zhang', 'san') #2
self.assertEqual(formatted_name, 'Zhang San') #3
if __name__ == '__main__':
unittest.main()
1处创建名为NameTestCase
的类,此类必须继承unittest.TestCase
类,这样Python才知道如何运行编写的测试
2处的调用函数get_formatted_name()
冰洁结果存入formatted_name
3处使用断言方法,核实得到的结果是否与期望一致。代码行self.assertEqual(formatted_name, 'Zhang San')
的意思是:将formatted_name的值同字符串‘Zhang San’进行比较,如果它们相等,就ok,如果不相等,跟我说一声。
运行结果:
1.3不能通过的测试
修改函数get_formatted_name()
为:
def get_formatted_name(first, middle, last):
"""生成格式工整的全名"""
full_name = first + ' ' + middle + ' ' + last
return full_name.title()
运行测试文件test_name_function.py
输出:
1.4测试未通过时怎么办
测试未通过时,不要修改测试,而应该修改导致测试不能通过的代码:检查刚对函数所作的修改,找出导致函数行为不符合预期的修改。
修改name_function.py
函数使之合理:
def get_formatted_name(first, last, middle=''):
"""生成格式工整的全名"""
if middle:
full_name = first + ' ' + middle + ' ' + last
else:
full_name = first + ' ' + last
return full_name.title()
运行测试代码:
1.5添加新测试
确定函数get_formatted_name()
又能正确处理简单姓名后,再编写一个测试用例测试包含中间名的姓名。为此,在NameTestCase
类中再添加一个方法:
class NameTestCase(unittest.TestCase):
"""测试name_function.py"""
def test_first_last_name(self):
"""能够正确地处理像zhang san这样的名字吗"""
formatted_name = get_formatted_name('zhang', 'feng', 'san')
self.assertEqual(formatted_name, 'Zhang San Feng')
formatted_name = get_formatted_name('zhang', 'san')
self.assertEqual(formatted_name, 'Zhang San')
def test_first_middle_last_name(self):
"""能够正确地处理像zhang san feng这样的名字吗"""
formatted_name = get_formatted_name('zhang', 'feng', 'san')
self.assertEqual(formatted_name, 'Zhang San Feng')
if __name__ == '__main__':
unittest.main()
这样如果测试未通过,就可以知道受影响的是哪类型的名字。
2测试类
第一部分为针对单个函数的测试,此部分为针对类的测试。
2.1各种断言方法
断言方法:检查认为应该满足的条件是否确实满足,如果该条件满足,对程序行为的假设就得到了确认,即可以确信其没有错误。若你所认为应该满足的条件并没有满足,Python将引发异常。
6种常用的断言方法:
方法 | 用途 |
---|---|
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中 |
2.2一个要测试的类
类的测试与函数的测试相似。
下面为帮助匿名调查的类。
survey.py
:
class AnonymousSurvey():
"""收集匿名调查问卷的答案"""
def __init__(self, question): #1
"""存储一个问题,并为存储答案做准备"""
self.question = question
self.responses = []
def show_question(self): #2
"""显示调查问卷"""
print(self.question) #5
def store_response(self, new_response): #3
"""存储单份调查答案"""
self.responses.append(new_response)
def show_results(self): #4
"""显示收集到的所有答案"""
print("问卷结果:")
for response in self.responses: #6
print('-' + response)
注意:
代码5处和6处的类内属性在使用时需要类内调用,即self.responses
为证明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()
AnonymousSurvey
类可以用于简单的匿名调查。假设将它放进survey
模块,并想进行改进:让每位用户都可输入多个答案;编写一个方法,它只列出不同的答案,并指出每个答案出现了多少次;再编写一个类,用于管理非匿名调查。
2.3 测试AnonymousSurvey
类
test_survey.py
:
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) #3
my_survey.store_response('English')
self.assertIn('English', my_survey.responses) #4
if __name__ == '__main__':
unittest.main()
要测试类的行为,需要创建其实例,3处创建my_survey
的实例,4处何时此答案是否被存储。
只收集一个答案的调查用途不大。下面何时用户提供三个答案时,它们也将被妥善地保存。为此,在TestAnonymousSurvey
类中再添加一个方法:
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)
#添加方法
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, responses)
if __name__ == '__main__':
unittest.main()
上面针对三个答案的测试使用循环确认答案都包含在列表中,但测试有些重复的地方,下面使用unittest
的另一项功能提高它们的效率。
2.4方法setUp()
unittest.TestCase
类中包含方法setUp()
,让测试模块只需要创建这些对象一次,并在每个测试方法中使用它们。
如果在TsetCase
类中使用方法setUp()
,Python将先运行它,再运行各个以test_
打头的方法,这样再编写的每个测试方法中都可以使用方法setUp()
中创建的对象了。
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def setUp(self):
"""创建一个调查对象和一组答案,供测试方法使用"""
self.question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(self.question) #1
self.responses = ['English', 'Spanish', 'Mandarin'] #2
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.my_survey.responses:
self.assertIn(response, self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
方法setUp()
做了两件事情:在1处,创建了一个调查对象,在2处创建了一个答案列表。
存储这两样东西的变量名包含前缀self
(即存储在属性中),因此可以在这个类的任何地方使用。