11.1 测试函数
首先编写待会儿进行测试的代码。
下面是一个简单的函数,它接受名和姓并返回整洁的姓名:
name_function.py
def get_formatted_name(first,last):
full_name=first+' '+last
return full_name.title()
names.py
#从name_function.py中导入get_formatted_name()
from name_function import get_formatted_name
print("Enter 'q' at anny time to quit.")
while True:
first=input("\nPlease enter your fisrt name:")
if first=='q':
break
last=input("\nPlease enter your last name: ")
if last=='q':
break
formatted_name=get_formatted_name(first,last)
print("\tNeatly formatted name: "+formatted_name+'.')
运行结果:
1.单元测试和测试用例
单元测试 用于核实函数的某个方面没有问题;
测试用例 是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。
全覆盖式测试 用例包含一整套单元测试,涵盖了各种可能的函数使用方 式。
通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。
2.可通过的测试
要为函数编写测试用例,可先导入模块unittest 以及要测试的函 数,再创建一个继承unittest.TestCase 的类,并编写一系列方法对函数行为的不同方面进行测试。
下面是一个只包含一个方法的测试用例,它检查函数get_formatted_name() 在给定名和姓时能否正确地工作:
test_name_function.py
import unittest
from name_function import get_formatted_name
#创建一个名为NamesTestCase的类,用于包含一系列针对get_formatted_name() 的单元测试
#这个类必须继承 unittest.TestCase 类
class NamesTestCase(unittest.TestCase):
"""测试name_function.py"""
def test_first_last_name(self):
formatted_name=get_formatted_name('janis','joplin')
self.assertEqual(formatted_name,'Janis Joplin')
unittest.main()
我们运行testname_function.py 时 , 所 有 以 test 打头的方法都将自动运行。在这个方法中,我们调用了要测试的函数,并存储了要测试的返回值。
unittest 类最有用的功能之一:一个断言方法,用于核实得到的值是否和期望值相同。
我们知道get_formatted_name() 应 返回这样的姓名,即名和姓的首字母为大写,且它们之间有一个空格,因此我们期望formatted_name 的值为Janis Joplin 。
为检查是否确实如此,我们调用unittest 的方法assertEqual() ,并向它传递formatted_name 和’Janis Joplin’ 。
代码行self.assertEqual(formatted_name, ‘Janis Joplin’) 的意思是 说:“formatted_name 的值同字符串’Janis Joplin’ 进行比较,如果它们相等,就ok,如果它们不相等,会反馈。”
代码行unittest.main() 让Python运行这个文件中的测试。
运行结果:
3.不能通过的测试
修改name_function.py
def get_formatted_name(first,middle,last):
full_name=first+' '+middle+' '+last
return full_name.title()
再次运行test_name_function运行结果
4.测试未通过时怎么办
下面来修改get_formatted_name() ,将中间名设置为可选的,然后再次运行这个测试用例。如果通过了,我们接着确认这个函数能够妥善地 处理中间名。
要将中间名设置为可选的,可在函数定义中将形参middle 移到形参列表末尾,并将其默认值指定为一个空字符串。我们还要添加一个if 测试,以便根据是否提供了中间名相应 地创建姓名:
def get_formatted_name(first,last,middle=''):
if middle:
full_name=first+' '+midlle+' '+last
else:
full_name=first+' '+last
return full_name.title()
运行结果:
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):
formatted_name=get_formatted_name('janis','joplin')
self.assertEqual(formatted_name,'Janis Joplin')
def test_first_last_middle_name(self):
formatted_name=get_formatted_name('robert','downey','john')
self.assertEqual(formatted_name,'Robert John Downey')
unittest.main()
运行结果:
练习
1 . 城市和国家 :编写一个函数,它接受两个形参:一个城市名和一个国家名。这个函数返回一个格式为City, Country 的字符串,如Santiago, Chile 。将 这个函数存储在一个名为city_functions.py的模块中。
创建一个名为test_cities.py的程序,对刚编写的函数进行测试(别忘了,你需要导入模块unittest 以及要测试的函数)。编写一个名为test_city_country() 的 方法,核实使用类似于’santiago’ 和’chile’ 这样的值来调用前述函数时,得到的字符串是正确的。运行test_cities.py ,确认测 试test_city_country() 通过了。
city_functions.py
def get_city_country(city,country):
msg=city+','+country
return msg.title()
test_cities.py
import unittest
from city_function import get_city_country
class CityCountryTest(unittest.TestCase):
def test_city_country(self):
city_country_msg=get_city_country('beijing','china')
self.assertEqual(city_country_msg,'Beijing,China')
unittest.main()
运行结果:
2.人口数量 :修改前面的函数,使其包含第三个必不可少的形参population ,并返回一个格式为City, Country - population xxx 的字符串, 如Santiago, Chile - population 5000000 。运行test_cities.py,确认测试test_city_country() 未通过。
def get_city_country(city,country,popluation):
msg=city+','+country+'-'+population
return msg.title()
运行结果:
修改上述函数,将形参population 设置为可选的。再次运行test_cities.py,确认测试test_city_country() 又通过了。
city_function.py
def get_city_country(city,country,popluation=''):
if popluation:
msg=city+','+country+'-'+population
else:
msg=city+','+country
return msg.title()
再次运行test_cities.py:
再编写一个名为test_city_country_population() 的测试,核实可以使用类似于’santiago’ 、‘chile’ 和’population=5000000’ 这样的值来调用 这个函数。再次运行test_cities.py,确认测试test_city_country_population() 通过了。
import unittest
from city_function import get_city_country
class CityCountryTest(unittest.TestCase):
def test_city_country(self):
city_country_msg=get_city_country('beijing','china')
self.assertEqual(city_country_msg,'Beijing,China')
def test_city_country_population(self):
city_country_msg=get_city_country('wuhan','china','80000')
self.assertEqual(city_country_msg,'Wuhan,China-80000')
unittest.main()
运行结果:
11.2测试类
1.各种断言方法
Python在unittest.TestCase 类中提供了很多断言方法。
方法 | 用途 |
---|---|
assertEqual(a,b) | 核实a==b |
assertNotEqual(a,b) | 核实a!=b |
assertTrue(x) | 核实x为True |
assertIn(item,list) | 核实item在list中 |
assertNotIn(item,list) | 核实item不在list中 |
2.一个要测试的类
类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处,下面来编写一个类进行测试。
来看一个帮助管理匿名调查的类:
class AnonymousSurvey():
"""收集匿名调查问卷的答案"""
def __init__(self,question):
""" 存储一个问题,并为存储答案做准备"""
self.question=question
self.responses=[]
def show_question(self):
""" 显示调查问卷"""
print(self.question)
def stored_response(self,new_response):
"""存储单份调查答案"""
self.responses.append(new_response)
def show_results(self):
"""显示收集到的所有答卷 """
print("Survey results:")
for response in self.responses:
print("-"+response)
language_survey.py
from survey import AnonymousSurvey
#定义一个问题,并创建一个表示调查的AnoymouSurvey对象
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.stored_response(response)
#显示调查结果
print("\n Thank u for participating in the survey!")
my_survey.show_results()
运行结果:
3.测试AnonymousSurvey类
下面来编写一个测试,对AnonymousSurvey 类的行为的一个方面进行验证:
如果用户面对调查问题时只提供了一个答案,这个答案也能被妥善地存储。为此,我们将在这个答 案被存储后,使用方法assertIn() 来核实它包含在答案列表中:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
""" 针对AnonyousSurvey类的测试"""
def test_stored_single_response(self):
""" 测试单个单元会被妥善的存储"""
question="What language did you first learn to speak?"
my_survey=AnonymousSurvey(question)
my_survey.stored_response('English')
self.assertIn('English',my_survey.responses)
unittest.main()
运行结果:
下面来核实用户提供三个答案时,它们也将被妥善地存储。
为此,我们在TestAnonymousSurvey 中再添加一个方法:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
""" 针对AnonyousSurvey类的测试"""
def test_stored_single_response(self):
""" 测试单个答案会被妥善的存储"""
question="What language did you first learn to speak?"
my_survey=AnonymousSurvey(question)
my_survey.stored_response('English')
self.assertIn('English',my_survey.responses)
def test_stored_three_responses(self):
"""测试三个答案会被妥善的存储"""
question="What language did you first learn to speak?"
my_survey=AnonymousSurvey(question)
responses=['chinese','english','french']
for response in responses:
my_survey.stored_response(response)
for response in responses:
self.assertIn(response,my_survey.responses)
unittest.main()
我们定义了一个包含三个不同答案的列表responses,再对其中每个答案都调用store_response() 。存储这些答案后,我们使用一个循环来确认每个答案都包含在my_survey.responses 中。
运行结果是两个测试都通过了:
4.方法setUp()
在前面的test_survey.py中,我们在每个测试方法中都创建了一个AnonymousSurvey 实例,并在每个方法中都创建了答案。
unittest.TestCase 类包含方法setUp() ,让我 们只需创建这些对象一次,并在每个测试方法中使用它们。如果你在TestCase 类中包含了方法setUp() ,Python将先运行它,再运行各个以test_打头的方法。这样,在你编写 的每个测试方法中都可使用在方法setUp() 中创建的对象了。
下面使用setUp() 来创建一个调查对象和一组答案,供方法test_store_single_response() 和test_store_three_responses() 使用: import unittest 。
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
""" 针对AnonyousSurvey类的测试"""
def setUp(self):
question="What language did you first learn to speak?"
self.my_survey=AnonymousSurvey(question)
self.responses=['chinese','english','french']
def test_stored_single_response(self):
""" 测试单个答案会被妥善的存储"""
self.my_survey.stored_response(self.responses[0])
self.assertIn(self.responses[0],self.my_survey.responses)
def test_stored_three_responses(self):
"""测试三个答案会被妥善的存储"""
for response in self.responses:
self.my_survey.stored_response(response)
for response in self.responses:
self.assertIn(response,self.my_survey.responses)
unittest.main()
练习
1.雇员:编写一个名为Employee 的类,其方法__init__() 接受名、姓和年薪,并将它们都存储在属性中。
class Employee:
def __init__(self,first,last,salary):
self.first=first
self.last=last
self.salary=salary
def give_raise(self,increment=5000):
self.salary+=increment
def show_employee(self):
print(self.first+' '+self.last+"'s salary:"+str(self.salary))
编写一个名为give_raise() 的方法,它默认将 年薪增加5000美元,但也能够接受其他的年薪增加量。 为Employee 编写一个测试用例,其中包含两个测试方法:test_give_default_raise() 和test_give_custom_raise() 。
import unittest
from employee import Employee
class TestEmployee(unittest.TestCase):
def test_give_default_raise(self):
my_employee=Employee('ming','jing',180000)
my_employee.give_raise()
my_employee.show_employee()
self.assertEqual(185000,my_employee.salary)
def test_give_custom_raise(self):
my_employee=Employee('ming','jing',180000)
my_employee.give_raise(8000)
my_employee.show_employee()
self.assertEqual(188000,my_employee.salary)
unittest.main()
运行结果:
使用方法setUp() ,以免在 每个测试方法中都创建新的雇员实例。运行这个测试用例,确认两个测试都通过了。
import unittest
from employee import Employee
class TestEmployee(unittest.TestCase):
def setUp(self):
self.my_employee=Employee('ming','jing',180000)
self.my_employee.show_employee()
def test_give_default_raise(self):
self.my_employee.give_raise()
self.my_employee.show_employee()
self.assertEqual(185000,self.my_employee.salary)
def test_give_custom_raise(self):
self.my_employee.give_raise(8000)
self.my_employee.show_employee()
self.assertEqual(188000,self.my_employee.salary)
unittest.main()
运行结果:
加上了shoe_employee打印雇员信息的方法,更加清晰的看到方法调用的顺序。