编写代码离不开写测试,只有通过测试才知道代码的逻辑有没有问题,健壮性好不好等,测试让你深信,即便有越来越多的人使用你的程序,它也能一如既往正确地工作。
1.测试函数
Python标准库中的模块unittest提供了代码测试工具。要进行相关测试,必须要有待测试的函数,创建文件name_function.py,其内容如下:
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()
1.1单元测试和测试用例
1)单元测试
用于核实某个函数的某个方面没有问题。
2)测试用例
是一组单元测试,它们一起核实函数在各种情况下的行为都符合要求。
3)全覆盖测试
测试用例包含一整套单元测试,涵盖了各种可能函数使用的方式。
1.2可通过的测试
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""Test the name_function.py"""
def test_first_last_name(self):
"""能够正确处理姓名吗?"""
formatted_name = get_formatted_name('sun','wukong')
self.assertEqual(formatted_name,'Sun Wukong')
if __name__ == '__main__':
unittest.main()
运行上面测试用例,运行结果:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
1.3未通过的测试
为通过测试很简单,将待测试的函数稍微修改下:
def get_formatted_name(first,last,middle = ''):
"""生成整洁的姓名"""
full_name = f"{first} {middle} {last}"
return full_name.title()
运行1.2中的测试用例,则会输出为通过的测试返回。笔者不再尝试。
1.4添加新测试
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
"""Test the name_function.py"""
def test_first_last_name(self):
"""能够正确处理姓名吗?"""
formatted_name = get_formatted_name('sun','wukong')
self.assertEqual(formatted_name,'Sun Wukong')
def test_first_last_middle_name(self):
"""测试三个名字"""
formatted_name = get_formatted_name('sun','wu','kong')
self.assertEqual(formatted_name,'Sun Kong Wu')
if __name__ == '__main__':
unittest.main()
运行结果:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
2.测试类
2.1各种断言方法
Python在unittest.TestCase类中提供了很多断言方法。下面列举几个常用的断言方法。
方法 | 用途 |
---|---|
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一个要测试的类
类的测试与函数的测试相似,类的测试基本上都是对方法的测试,不过也存在一些不同之处,下面创建一个匿名调查类来进行测试。
class AnonymousServey:
"""收集匿名调查问卷的答案"""
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_result(self):
"""显示收集到的所有答案"""
print("Survey results:")
for response in self.responses:
print(f"- {response}")
下面编写一个测试,对
AnonymousServey
类的行为一个方面进行验证:核实答案列表中是否有指定答案。
import unittest
from survey import AnonymousServey
class TestAnonymousServey(unittest.TestCase):
"""针对AnonymousServey进行测试"""
def test_store_single_response(self):
question = "What language did you first learn to speak?"
self.my_survey = AnonymousServey(question)
self.my_survey.store_response('python')
self.assertIn('python',self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
上面的测试类先导入
unittest
和要测试的
AnonymousServey
,测试调查问卷的被存储后,测试指定数据是否包含在存储的列表中。我们还可以添加更多维度的测试case,例如测试指定某列表是否包含在存储的列表中。
2.3方法setUp()
如果继续添加测试方法,会有很多重复,如何让公共的变量或者测试数据,在用例执行前就能声明好,而且其他方法都可以公用,如何解决呢?可以使用
setUp()
函数。结合上面的测试用例,看看如何使用
setUp()
函数
import unittest
from survey import AnonymousServey
class TestAnonymousServey(unittest.TestCase):
"""针对AnonymousServey进行测试"""
def setUp(self):
"""创建一个调查对象和一组答案,供测试方法使用"""
question = "What language did you first learn to speak?"
self.my_survey = AnonymousServey(question)
self.tree_responses = ['java','php','python']
def test_store_single_response(self):
self.my_survey.store_response(self.tree_responses[0])
self.assertIn(self.tree_responses[0],self.my_survey.responses)
def test_store_tree_response(self):
for response in self.tree_responses:
self.my_survey.store_response(response)
for response in self.tree_responses:
self.assertIn(response,self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
方法
setUp()
做了两件事:创建一个调查对象,以及创建一个答案列表。存储这两样东西的变量名包含前缀
self
(即存储在属性中),因此可以类的任何地方进行使用。
注意:单元测试对于一个程序的健壮形特别重要。但是要想要自己的单元测试覆盖自己所写的每个方法,每一行却不是很容易,需要各种条件的设置,对于第三方的调用还可以使用Mock方式进行,此是后话了。