Python 测试代码
- 通过测试,可确定代码面对各种输入都能够按要求的那样工作
- 序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。
1、测试函数:
-
要学习测试,得有要测试的代码
-
首先再当前目录下创建一个name_fun.py的文件,内容如下:
def get_test_name(first, last): """测试名字的代码""" full_name = first + ' ' + last return full_name.title()
-
再同级目录中再创建.py执行文件:
from name_fun import get_test_name # 先导入测试名字代码的模块 while True: first = raw_input("请输入您的姓:") if first == 'q': break last = raw_input('请输入您的名:') if last == 'q': break full_name = get_test_name(first, last) # 调用该模块 print(full_name)
- 这里python 2使用raw_input()
-
现在假设我们要修改get_test_name() ,使其还能够处理中间名,这样做时,我们要确保不破坏这个函数处理只有名和姓的姓名的方式。为此,我们可以在每次修改get_test_name() 后都进行测试,并再次输入first,last,这太繁琐了。
-
-
所幸Python提供了一种自动测试函数输出的高效方式。
1.1、单元测试和测试用例:
- Python标准库中的模块
unittest 提供了代码测试工具
单元测试
用于核实函数的某个方面没有问题
测试用例是一组单元测试
,这些单元测试一起核实函数在各种情形下的行为都符合要求
全覆盖式测试
用例包含一整套单元测试
,涵盖了各种可能的函数使用方式。
1.2、可通过的测试:
-
要为函数编写测试用例,可先
导入模块unittest 以及要测试的函数,再创建一个继承unittest.TestCase 的类
-
下面是一个只包含一个方法的测试用例:
import unittest from name_fun import get_test_name class NamesTestCase(unittest.TestCase): """测试name_fun.py""" def test_first_last_name(self): """能够正确的处理姓名""" full_name = get_test_name('fe', 'cow') self.assertEqual(full_name, 'Fe Cow') unittest.main()
-
首先,我们导入了
模块unittest
和要测试的函数get_test_name()
-
我们创建了一个
名为NamesTestCase 的类(名字最好有意义)
,用于包含一系列针对get_test_name() 的单元测试,
这个类必须继承unittest.TestCase 类
,这样Python才知道如何运行你编写的测试。
-
NamesTestCase
只包含一个方法
,用于测试get_test_name() 的一个方面- 我们将这个方法命名为test_first_last_name() ,因为我们要核实的是只有名和姓的姓名能否被正确地格式化
-
我们运行.py执行模块时,所有以test 打头的方法都将自动运行,这个方法中,我们调用了要测试的函数,并存储了要测试的返回值
-
我们使用
实参'fe', 'cow'调用get_test_name()
,并将结果存储到变量full_name中
-
unittest 类最有用的功能之一。
断言 方法
断言方法用来核实得到的结果是否与期望的结果一致
- 为检查是否确实如此,我们
调用unittest的方法assertEqual()
-
代码行self.assertEqual(full_name, ‘Fe Cow’) 的意思是说:
将full_name的值同字符串'Fe Cow' 进行比较,如果它们相等,就大吉大利,今晚吃鸡,如果不相等就gg
-
运行
unittest.main() 让Python运行这个文件中的测试
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK 表明该测试用例中的所有单元测试都通过了。
-
1.3、不能通过的测试:
-
测试未通过时结果是什么样的呢?
def get_test_name(first, middle, last): """测试名字的代码""" full_name = first + ' ' + last return full_name.title()
-
我们修改了测试名字的函数,加了个中间名,再次运行代码:
E ====================================================================== ERROR: test_first_last_name (__main__.NamesTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "C:/Users/lh9/PycharmProjects/request/my_test01.py", line 1060, in test_first_last_name full_name = get_test_name('fe', 'cow') TypeError: get_test_name() takes exactly 3 arguments (2 given) ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (errors=1)
- 其中包含的信息很多,因为测试未通过时,需要让你知道的事情可能有很多
第1行输出只有一个字母E
,它指出测试用例中有一个单元测试导致了错误
。接下来,我们
看到NamesTestCase 中的test_first_last_name() 导致了错误
- 测试用例包含众多单元测试时,知道哪个测试未通过至关重要
- 我们看到了一个
标准的traceback,它指出函数调用get_test_name('fe', 'cow') 有问题,因为它缺少一个必不可少的位置实参。
- 最后,还看到了一条消息,它指出整个测试用例都未通过,因为运行该测试用例时发生了一个错误,
FAILED (errors=1)
-
1.4、测试未通过时怎么办?
-
测试未通过时,不要修改测试,而应修复导致测试不能通过的代码
- 检查刚对函数所做的修改,找出导致函数行为不符合预期的修改
-
get_test_name()以前只需要两个实参,名和姓,但现在它要求提供名、中间名和姓。新增的中间名参数是必不可少的,这导致get_test_name() 的行为不符合预期。
最佳的选择是让中间名变为可选的
-
要将中间名设置为可选的,可在函数定义中将形参middle 移到形参列表末尾,并将其默认值指定为一个空字符串
def get_test_name(first, last, middle=''): """测试名字的代码""" if middle: full_name = first + ' ' + middle + ' ' + last else: full_name = first + ' ' + last return full_name.title()
-
再测试用力代码中:
class NamesTestCase(unittest.TestCase): def test_first_last_name(self): full_name = get_test_name('fe', 'cow') self.assertEqual(full_name, 'Fe Cow') unittest.main() # 输出结果如下: . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
-
1.5、添加新测试:
-
确定get_test_name() 又能正确地处理简单的姓名后,我们再编写一个测试,用于测试包含中间名的姓名
class NamesTestCase(unittest.TestCase): """测试用例""" def test_first_last_name(self): """测试名字跟姓""" full_name = get_test_name('fe', 'cow') self.assertEqual(full_name, 'Fe Cow') def test_first_middle_last_name(self): """测试名字中间名跟姓""" full_name = get_test_name('fe', 'cow', 'big') self.assertEqual(full_name, 'Fe Big Cow') unittest.main() # 输出结果如下: .. ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK # 两个测试用例都通过了
- 我们将这个方法命名为test_first_middle_last_name() 。
方法名必须以test_打头
,这样它才会在我们运行.py时自动运行
在TestCase 类中使用很长的方法名是可以的
这- 些方法的名称必须是描述性的,这才能让你明白测试未通过时的输出
- 我们将这个方法命名为test_first_middle_last_name() 。
2、测试类:
- 前半部分,你编写了针对单个函数的测试,下面来
编写针对类的测试
2.1、各种断言方法:
-
Python在
unittest.TestCase 类中提供了很多断言方法
断言方法检查你认为应该满足的条件是否确实满足
-
6个常用的断言方法
-
使用这些
方法可核实返回的值等于或不等于预期的值、返回的值为True 或False 、返回的值在列表中或不在列表中
-
你只能在
继承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 AnonymousSurvey(): """收集匿名调查问卷的答案""" def __init__(self, question): """存储一个问题,并为存储答案做准备""" self.question = question self.response = [] def show_question(self): """显示调查问卷""" print(self.question) def store_response(self, new_response): """存储单份调查答卷""" self.response.append(new_response) def show_results(self): """显示收集到的所有答卷""" for response in self.response: print('-' + response)
-
为证明AnonymousSurvey 类能够正确地工作,我们来编写一个使用它的程序
from name_fun import AnonymousSurvey question = '这是一个很严肃的问题!' my_survey = AnonymousSurvey(question) my_survey.show_question() # 显示问题并存储答案 while True: response = raw_input('请提出你的质疑:') if response == 'q': break my_survey.store_response(response) print('\n显示调查结果') my_survey.show_results()
-
2.3、测试AnonymousSurvey 类:
-
编写一个测试,
对AnonymousSurvey 类的行为的一个方面进行验证
,使用方法assertIn() 来核实它包含在答案列表中
from name_fun import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): """针对AnonymousSurvey类的测试""" def test_store_single_response(self): """测试单个答案被妥善的存储""" question = 'That is a question' my_survey = AnonymousSurvey(question) my_survey.store_response('English') self.assertIn('English', my_survey.response) unittest.main() # 输出结果: . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
- 我们首先
导入了模块unittest
以及要测试的类AnonymousSurvey
- 我们将
测试用例命名为TestAnonymousSurvey
,它也继承了unittest.TestCase
- 第一个测试方法验证调查问题的单个答案被存储后,会包含在调查结果列表中
- 要测试类的行为,需要创建其实例
- 然后使用方法store_response() 存储了单个答案English
- 接下来,我们检查English 是否包含在列表my_survey.responses 中,以核实这个答案是否被妥善地存储了
- 我们首先
-
下面核实用户提供三个答案时:
class TestAnonymousSurvey(unittest.TestCase): """针对AnonymousSurvey类的测试""" def test_store_three_response(self): """测试单个答案被妥善的存储""" question = 'That is a question' my_survey = AnonymousSurvey(question) responses = ['Spanish', 'Mandarin', 'English'] for response in responses: my_survey.store_response(response) for response in responses: self.assertIn(response, my_survey.response) unittest.main() # 输出结果如下: . ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
-
做法的效果很好,
但这些测试有些重复的地方
。下面使用unittest 的另一项功能来提高它们的效率
2.4、方法setUp():
unittest.TestCase 类包含方法setUp()
,让我们只需创建这些对象一次
,并在每个测试方法中使用它们
- 如果你在
TestCase 类中包含了方法setUp()
,Python将先运行它
,再运行各个以test_打头的方法
。
import unittest
from name_fun import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试"""
def setUp(self):
"""
创建一个调查对象和一组答案
供使用的测试方法使用
"""
question = 'That is a question'
self.my_survey = AnonymousSurvey(question)
self.responses = ['Spanish', 'Mandarin', 'English']
def test_store_single_response(self):
"""测试单个答案被妥善的存储"""
self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.response)
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.response)
unittest.main()
# 输出结果如下:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
方法setUp() 做了两件事情
:创建一个调查对象;创建一个答案列表,存储这两样东西的变量名包含前缀self
,因此可在这个类的任何地方使用
。- 方法test_store_three_response() 核实self.responses 中的第一个答案—self.responses[0] —被妥善地存储,而方法test_store_three_response() 核实self.responses 中的全部三个答案都被妥善地存储。
- 测试自己编写的类时,
方法setUp() 让测试方法编写起来更容易
可在setUp() 方法中创建一系列实例并设置它们的属性
,再在测试方法中直接使用这些实例。- 相比于在每个测试方法中都
创建实例并设置其属性
,这要容易得多。
- 注意 :运行测试用例时,每完成一个单元测试,Python都打印一个字符:
测试通过时打印一个句点
;测试引发错误时打印一个E
;测试导致断言失败时打印一个F
。这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因
。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。