《Python编程:从入门到实践》 第 11 章 测试代码

第 11 章 测试代码

这章学习使用 Python 的 unittest 模块;编写测试用例去核实预期的输出。还会看到测试通过与未通过的样子,与如何测试函数和类,并知道该为项目编写多少个测试。

11.1 测试函数

下面是个简单的函数:

1
2
3
4
def get_formatted_name(first, last):
"""Generate a neatly formatted full name."""
full_name = first + ' ' + last
return full_name.title()

11.1.1 单元测试和测试用例

Python 标准库中的模块 unittest 提供了代码测试工具。
单元测试:用于核实函数的某个方面没有问题。
测试用例:是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。
全覆盖式测试:用例包含一整套单元测试,涵盖了各种可能的函数使用方式。

11.1.2 可通过的测试

编写函数测试用例的步骤:

  1. 导入 unittest 模块;
  2. 导入要测试的函数;
  3. 创建个集成 unittest.TestCase 的类;
  4. 编写一系列方法对函数的不同方面进行测试。

下面是检查函数 get_formatted_name() 是否正常工作的用例:

1
2
3
4
5
6
7
8
9
10
11
12
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()

测试类的名子最好以 TestCase 结尾。测试类中的方法名都需要以 test 开头,因为只有 test 开头的方法才会被自动调用。assertEqualunittest 类最有用的功能之一:断言方法。断言方法用来核实得到的结果是否与期望的一致。代码行 unittest.main() 将运行上面代码中以 test 开头的方法,并得到的输出如下:

1
2
3
4
5
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

11.1.3 不能通过的测试

修改 get_formatted_name() 函数,增加一个指定中间名的实参:

1
2
3
4
def get_formatted_name(first, middle, last):
"""生成整洁的姓名"""
full_name = first + ' ' + middle + ' ' + last
return full_name.title()

因为增加了参数,再运行上面测试代码时,输入如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

上面的最后一行表示整个测试用例未通过,因为运行时发生了一个错误。

11.1.4 测试未通过时怎么办

不要修改测试!去检查对函数的修改,找出不符合预期的问题。

11.1.5 添加新测试

这里没什么,只是增加了个用来测试包含中间名的方法。

11.2 测试类

下面会编写针对类的测试。

11.2.1 各种断言方法

方法 用途
assertEqual(a, b) 核实 a == b
assertNotEqual(a, b) 核实 a != b
assertTrue(x) 核实 xTrue
assertFalse(x) 核实 xFalse
assertIn(item, list) 核实 itemlist
assertNotIn(item, list) 核实 item 不在 list

11.2.2 一个要测试的类

类的测试与函数类似,大部分工作都是测试类中的方法的行为。

这里的代码写了业务类和调用。

暂时略过…

11.2.3 测试AnonymousSurvey 类

这里也没什么只是验证列表中是否有个被某方法放入的元素。

11.2.4 方法setUp()

如果你在TestCase类中包含了方法 setUp(),Python 将先运行它,再运行各个以 test_ 打头的方法。这样,就可在每个测试方法中使用 方法 setUp() 中创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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()

注意:运行测试用例时,每完成一个单元测试,Python 都会打印一个字符:测试通过打印句点;错误打印 E;断言失败打印 F。

11.3 小结

  • 使用模块 unittset 中的工具为函数和类编写测试;
  • 如何编写集成 unittest.TestCase 的类,以及如何编写测试方法,核实函数和类的行为符合预期;
  • 如何使用 setUp() 创建实例与设置属性,以便在类的所有测试方法中公用。