python学习笔记(十八)测试函数
1、测试初见
每一个具有实用意义的程序都需要编写函数或者类,但是从主观角度,我们往往难以判断这些函数或者类面对不同的输入,是否都能够按要求进行操作。
幸运的是,python中的模块unittest为我们提供了相应的测试工具,我们可以根据这些工具编写测试代码,在用户发现问题之前就把它们测试出来。这里我们简单了解一下有关测试的几个概念:
a. 单元测试:用于核实函数的某个方面没有问题,注意这里的“某个方面”,编写单元测试最主要的地方就在于“不要贪”。
b. 测试用例:一组单元测试。测试用例的编写原则应当是综合考虑函数可能收到的各种输入。
c. 全覆盖式测试:包含一整套单元测试。全覆盖式测试的编写原则应当是包含各种可能的函数使用方式。
2、测试通过
下面我们根据例子来简单的学习一下测试代码的编写,首先,创建一个空文件夹day_18。
下面在文件 name_function.py 中编写待测试函数get_formatted_name(),并将其存放在文件夹day_18中。
def get_formatted_name(first, last):
"""将传入的名和姓合并成完整姓名,并在名和姓之间加上一个空格,将其首字母大写,最终将结果返回"""
full_name = first + ' ' + last
return full_name.title()
接下来我们重新创建一个 test_name_function.py 文件(存放在day_18中),编写测试代码。
import unittest
from name_function import get_formatted_name
class NameTestCase(unittest.TestCase):
"""测试name_function.py"""
def test_first_last_name(self):
"""测试能否正确处理样例姓名"""
formatted_name = get_formatted_name('python', 'java')
self.assertEqual(formatted_name, 'Python Java')
unittest.main()
我们先来看代码,编写测试代码文件一般有一些固定的小套路:
a. 导入模块unittest,因为我们在编写测试类的时候需要继承模块unittest中的类(主要是TestCase,为了告诉python如何运行我们编写的测试)。
b. 导入待测试函数。
c. 创建测试类(这个类的命名是随意的,但是最好联系待测试函数,并包含"Test"),继承unittest.TestCase,编写一系列方法测试函数行为的不同方面(注意不重不漏)。这里注意,当我们运行测试代码文件时,所有以 test_ 开头的方法都将自动运行。
d. 测试文件的最后一般都是 unittest.main() ,它让python运行这个文件中的测试。
上述代码只自定义了一个方法,因此它只用于测试待测试函数的一个方面(只有名和姓的输入能否被正确地操作)。这个方法中最重要的就是我们使用了unittest类最有用的功能之一——断言方法(用来核实得到的结果是否与期望值一致)。接下来我们来看运行结果:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
第一行的句点表示有一个测试通过了,然后指出python运行一个测试所消耗的时间,最后的OK表明该测试用例中的所有单元测试都通过了。
3、测试未通过
下面我们改写 name_function.py 文件中的 get_formatted_name() 函数,看一下测试未通过的运行结果。
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)
测试能否正确处理样例姓名
----------------------------------------------------------------------
Traceback (most recent call last):
File "/(老规矩,文件路径手动打码)/day_18/test_name_function.py", line 9, in test_first_last_name
formatted_name = get_formatted_name('python', 'java')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
第一行的E指出测试用例中有一个单元测试导致了错误,然后具体指出是哪一个单元测试。后面就是我们在学习异常时的老朋友——traceback,异常报告指出引发异常的原因。“Ran 1 test in 0.000s”还是python运行一个测试所耗的时间,最后一行的“FAILED (errors=1)”则表明整个测试用例都未通过,因为运行该测试用例时发生了一个错误。
测试未通过就说明测试的那部分代码是错误的,这时候千万不要想着去修改测试,而是根据错误报告调试不能通过测试的代码,现在我们来修正 get_formatted_function() 函数。
def get_formatted_name(first, last, middle=''):
"""生成完整的名字"""
if middle:
full_name = first + ' ' + middle + ' ' + last
else:
full_name = first + ' ' + last
return full_name.title()
这里我们不要想着把函数改回第二点中的例子,因为中间名是常态,我们应该处理好这个问题,这里使用的方法是运用默认值将middle形参变为可选的。保存修改,重新运行 test_name_function.py 文件,运行结果为:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
测试用例通过,函数编写成功!
4、添加新测试
一个测试类一般都不止一个方法,因为我们我们需要综合考虑函数的所有输入情况,下面我们来完善一下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):
"""测试能否正确处理样例姓名"""
formatted_name = get_formatted_name('python', 'java')
self.assertEqual(formatted_name, 'Python Java')
def test_first_last_middle_name(self):
formatted_name = get_formatted_name('python', 'java', 'ruby')
self.assertEqual(formatted_name, 'Python Ruby Java')
unittest.main()
注意新增方法也要以 test_ 开头(否则无法在运行文件时自动运行),运行结果为:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
测试通过,函数编写成功!