11 第十一章 测试代码
测试函数
Python标准库中的模块unittest 提供了代码测试工具;
单元测试 用于核实函数的某个方面没有问题;
测试用例 是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求;
全覆盖式测试 用例包含一整套单元测试,涵盖了各种可能的函数使用方式;
对于大型项目,要实现全覆盖可能很难;
通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖
# name_function.py
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = first + ' ' + last
return full_name.title()
# 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 + '.')
# test_name_function.py
import unittest
from name_function import get_formatted_name
class NamesTestCase(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()
使用了unittest 类最有用的功能之一:一个断言 方法;
断言方法用来核实得到的结果是否与期望的结果一致
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
第1行的句点表明有一个测试通过了;
接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒;
最后的OK 表明该测试用例中的所有单元测试都通过了
# name_function.py
# 运行test_name_function.py
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = first + ' ' + middle + ' ' + last
return full_name.title()
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)
第1行输出只有一个字母E,它指出测试用例中有一个单元测试导致了错误;
接下来,我们看到NamesTestCase 中的test_first_last_name() 导致了错误;
测试用例包含众多单元测试时,知道哪个测试未通过至关重要;
我们看到了一个标准的traceback,它指出函数调用get_formatted_name('janis', 'joplin') 有问题,因为它缺少一个必不可少的位置实参;
我们还看到运行了一个单元测试;
最后,还看到了一条消息,它指出整个测试用例都未通过,因为运行该测试用例时发生了一个错误;
这条消息位于输出末尾,让你一眼就能看到——你可不希望为获悉有多少测试未通过而翻阅长长的输出
# 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
import unittest
from name_function import get_formatted_name
class NamesTestCase(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()
在TestCase 类中使用很长的方法名是可以的;
这些方法的名称必须是描述性的,这才能让你明白测试未通过时的输出
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
测试类
# survey.py
class AnonymousSurvey():
"""收集匿名调查问卷的答案"""
def __init__(self, question):
"""存储一个问题,并为存储答案做准备"""
self.question = question
self.responses = []
def show_question(self):
"""显示调查问卷"""
print(question)
def store_response(self, new_response):
"""存储单份调查答卷"""
self.responses.append(new_response)
def show_results(self):
"""显示收集到的所有答卷"""
print("Survey results:")
for response in 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()
# test_survey.py
import unittest
from survey import AnonymousSurvey
class TestAnonmyousSurvey(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)
unittest.main()
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
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, my_survey.responses)
unittest.main()
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
unittest.TestCase 类包含方法setUp() ,让我们只需创建对象一次,并在每个测试方法中使用它们;
如果你在TestCase 类中包含了方法setUp() ,Python将先运行它,再运行各个以test_打头的方法;
这样,在你编写的每个测试方法中都可使用在方法setUp() 中创建的对象了
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_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)
unittest.main()
可在setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例;
相比于在每个测试方法中都创建实例并设置其属性,这要容易得多
运行测试用例时,每完成一个单元测试,Python都打印一个字符:
测试通过时打印一个句点;
测试引发错误时打印一个E;
测试导致断言失败时打印一个F
小结
如何使用模块unittest 中的工具来为函数和类编写测试;
如何编写继承unittest.TestCase 的类,以及如何编写测试方法,以核实函数和类的行为符合预期;
如何使用方法setUp() 来根据类高效地创建实例并设置其属性,以便在类的所有测试方法中都可使用它们;
参与工作量较大的项目时,你应对自己编写的函数和类的重要行为进行测试;
这样你就能够更加确定自己所做的工作不会破坏项目的其他部分,你就能够随心所欲地改进既有代码了;
如果不小心破坏了原来的功能,你马上就会知道,从而能够轻松地修复问题;
相比于等到不满意的用户报告bug后再采取措施,在测试未通过时采取措施要容易得多;
如果你在项目中包含了初步测试,其他程序员将更敬佩你,他们将能够更得心应手地尝试使用你编写的代码,也更愿意与你合作开发项目;
如果你要跟其他程序员开发的项目共享代码,就必须证明你编写的代码通过了既有测试,通常还需要为你添加的新行为编写测试;
请通过多开展测试来熟悉代码测试过程;
对于自己编写的函数和类,请编写针对其重要行为的测试,但在项目早期,不要试图去编写全覆盖的测试用例,除非有充分的理由这样做