学习目标
- 如何使用模块unittest中的工具为函数和类编写测试
- 如何编写继承unittest.TestCase的类,以及如何编写测试方法,以核实函数和类的行为符合预期
- 如何使用setUp()来根据类高效地创建实例并设置其属性,以便在类的所有测试方法中都可以使用它们
1. 测试函数
要学习测试,得要有测试的代码。下面是简单的函数,它接收名和姓并返回整洁的姓名。
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('\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("\tNeatly formatted name:" + formatted_name + ".")
模拟用户输入名和姓,可以看到全名,以下是程序的运行结果:
Enter 'q' at any time to quit.
Please give me a first name:janis
Please give me a last name:joplin
Neatly formatted name:Janis Joplin.
Please give me a first name:q
从上述输出可知,合并的全名正确。现在假如我们需要修改函数,使其还能够处理中间名。同时不破坏只有名和姓的处理方式。为此,我们需要修改函数后都进行测试:运行程序names.py,输入Janis Joplin这样的姓名,但这样太繁琐了。所幸python提供了一种自动化测试函数的高效方式。倘若我们对函数进行自动化测试,就能始终信心满满,确信函数能够正确的工作。
1.1 单元测试和测试用例
python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;测试用例是一组单元测试。全覆盖式测试用例包括一整套单元测试,涵盖了各种可能的函数使用方式。通常,最初只要对代码的重要行为编写测试即可。
1.2 可通过的测试
以下是一个只包含一个方法的测试用例,它检查函数能否正确工作。
test_name_function.py
import unittest
from name_function import get_formatted_name
class NameTestCase(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')
unittest.main()
以下是运行结果:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
1.3 不能通过的测试
测试未通过结果如何?我们来修改函数,使其能够处理中间名,下面是函数的新版本。
name_function.py
def get_formatted_name(first, middle, last):
"""生成整洁的姓名"""
full_name = first + ' ' + middle +' ' + last
return full_name.title()
运行测试代码test_name_function.py时,输出如下:
E
======================================================================
ERROR: test_first_last_name (__main__.NameTestCase)
能够正确处理想Janis Joplin这样的姓名吗
----------------------------------------------------------------------
Traceback (most recent call last):
File "G:/python/test_name_function.py", line 9, 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.001s
FAILED (errors=1)
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()
再次运行test_name_function.py:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
现在测试用例通过了
1.5 添加新测试
在编写一个测试,用于测试包含中间名的姓名。为此,我们在NamesTestCase类中再添加一个方法:
test_name_function.py
import unittest
from name_function import get_formatted_name
class NameTestCase(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')
unittest.main()
我们再次运行该代码时,测试都通过了:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
2. 测试类
下面编写针对类的测试
2.1 各种断言方法
方法 | 用途 |
---|---|
assertEqual(a,b) | 核实a == b |
assertNotEqual(a,b) | 核实a != b |
assertTrue(x) | 核实x为True |
assertFalse(x) | 核实x为Flase |
assertIn(item,list) | 核实item在list中 |
assertNotIn(item,list) | 核实item不在list中 |
2.2 一个要测试的类
下面编写一个匿名调查的类进行测试。
survey.py
class AnonymousSurvey(object):
"""收集匿名调查问卷的答案"""
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)
我们编写一个来使用它的程序
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()
运行结果:
What language did you first learn to speak?
Enter 'q' at any time to quit.
language:Chinese
language:English
language:Spanish
language:Mandarin
language:q
Thank you to everyone who participated in the survey!
Survey results:
-Chinese
-English
-Spanish
-Mandarin
2.3 测试AnonymousSurvey类
编写一个测试,用assertIn()核实它是否在答案列表中。先写一个方法,运行后再添加第二个方法。
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)
my_survey.store_response("English")
self.assertIn('English', my_survey.response)
def test_store_three_response(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()
运行结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
前述做法效果很好,但这些测试有重复的地方。下面使用unittest的另一项功能提高他们的效率
2.4 方法setUp()
在TestCase类中setUp()是优先运行的方法。使用setUp()来创建一个调查对象和一组答案,供内部两个方法使用。
test_survey.py
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_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()
运行结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
扩展
运行测试用例时,每完成一个单元测试,python都打印一个字符:
- 测试通过时,打印一个句点 ‘.’
- 测试引发错误时,打印一个字母 ‘E’
- 测试导致断言失败时,打印一个字母 ‘F’
这就是你运行测试用例时,在输出第一行看到句点和字符数量各不相同的原因。