此系列文章的创作初衷是作为读书过程中的笔记,而非教程类文章。
第11章 测试代码
11.1 测试函数
- Python标准库中的unittest模块提供了代码测试工具。单元测试用于测试函数在某个方面的行为;测试用例是一组单元测试,测试了函数在各种情形下的行为是否都符合要求。全覆盖测试用例包含一整套单元测试,涵盖了函数可能遇到的各种情形。通常,最初只要对函数的重要行为编写测试代码,等到项目被广泛使用时再考虑全覆盖测试。
- 下面的函数接受姓和名,或接受姓、名和中间名,并返回格式化的形式,这是一会要测试的函数。
def formatName(firstName: str, lastName: str, middleName: str = "") -> str:
if middleName == "":
return firstName.title() + " " + lastName.title()
else:
return firstName.title() + " " + middleName.title() + " " + lastName.title()
为了测试该函数,新建一个名为TestName.py的文件,在其中导入unittest和需要测试的函数,然后编制测试代码如下:
import unittest
from main import formatName
class NameTestCase(unittest.TestCase):
def test_FirstLastName(self):
str = formatName("jane", "eyre")
self.assertEqual("Jane Eyre", str)
def test_FirstMiddleLastName(self):
str = formatName("wolfgang", "mozart", "amadeus")
self.assertEqual("Wolfgang Amadeus Mozart", str)
unittest.main()
- 类NameTestCase是为函数formatName()编写的测试用例,它必须继承unittest.TestCase类。测试用例类的命名是任意的,但建议包含“TestCase”以指明这是一个测试用例。
- 类NameTestCase中含有若干单元测试,它们都以“test”开头,这样的函数在运行TestName.py文件时将被自动执行。在测试用例中编写的函数应该具有描述性的函数名(即使这样可能让函数名很长),以明确函数测试了被测试函数的哪种行为。函数中用到了断言方法来核实得到的结果是否与期望的结果一致。
- 在文件的最后,不要忘记调用unittest.main()来启动测试。运行上面的单元测试,将在控制台得到下面的输出。这里的两个点表明通过了两个单元测试,末尾的OK代表通过了所有的单元测试。
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
- 现在修改测试用例,故意让其认为函数出现了错误。输出中的“.F”表明第一个测试通过,第二个测试未通过;之后,Python输出了错误信息;并在最后告诉我们没有通过所有的单元测试,以及有几个单元测试未通过。
class NameTestCase(unittest.TestCase):
def test_FirstLastName(self):
str = formatName("jane", "eyre")
self.assertEqual("Jane Eyre", str)
def test_FirstMiddleLastName(self):
str = formatName("wolfgang", "mozart", "amadeus")
self.assertEqual("Jane Eyre", str)
.F
======================================================================
FAIL: test_FirstMiddleLastName (__main__.NameTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "E:/项目/Python/PythonExp/TestName.py", line 12, in test_FirstMiddleLastName
self.assertEqual("Jane Eyre", str)
AssertionError: 'Jane Eyre' != 'Wolfgang Amadeus Mozart'
- Jane Eyre
+ Wolfgang Amadeus Mozart
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)
11.2 测试类
- 除assertEqual(),unittest模块还提供了其他断言方法,这些断言方法只能在继承unittest.TestCase的类中使用。下面列出了6个常见的断言方法。
方法 | 说明 |
---|---|
assertEqual (a: any, b: any) | 核实a==b |
assertNotEqual (a: any, b: any) | 核实a!=b |
assertTrue (x: any) | 核实x为True |
assertFalse (x: any) | 核实x为False |
assertIn (item: any, container: Union[Iterable, Container]) | 核实item在container中,container可以是列表、字典(核实键)、元组、字符串(核实子串)、文件对象(当对象为文本文件时,核实一行文本)等 |
assertNotIn (item: any, container: Union[Iterable, Container]) | 核实item不在container中 |
- 测试类与测试函数大致相同——大多数工作都是在测试类中的方法,但有些许不同。编写下面的类来说明测试类的方法。
class AnonymousSurvey():
"""
收集匿名调查的答案
"""
def __init__(self, question):
self.question = question
self.responses = []
def collectResponses(self) -> None:
print(self.question)
print("Enter \"q\" at any time to quit.")
while True:
response = input("You Response: ")
if response != "q":
self.storeResponse(response)
else:
print("Thank you all for participating in the survey!")
break
def storeResponse(self, response: str) -> None:
self.responses.append(response)
def showResults(self) -> None:
print("Survey Results:")
for response in self.responses:
print("- " + response)
- 现在要测试storeResponse()方法。为了验证在一个人或者多个人回答问题时,程序都能将回答正确地保存进responses列表,可以编写下面的测试代码:
import unittest
from main import AnonymousSurvey
class SurveyTestCase(unittest.TestCase):
def test_OneResponse(self):
question = "What programming language did you first learn to use?"
survey = AnonymousSurvey(question)
survey.storeResponse("C++")
self.assertIn("C++", survey.responses)
def test_ThreeResponses(self):
question = "What programming language did you first learn to use?"
survey = AnonymousSurvey(question)
responses = ["C++", "Java", "Python"]
for response in responses:
survey.storeResponse(response)
for response in responses:
self.assertIn(response, survey.responses)
unittest.main()
在这个测试用例里面,为每个单元测试都构造了一个AnonymousSurvey对象,而这两个对象的问题是相同的。可以预见,如果要进行大量测试,可能需要大量重复的工作。为此,可以借助unittest的setUp()方法。setUp()方法在任何单元测试开始之前运行,在setUp()方法中可以建立若干个对象,供之后所有的单元测试使用。使用setUp()的测试代码如下:
import unittest
from main import AnonymousSurvey
class SurveyTestCase(unittest.TestCase):
def setUp(self):
question = "What programming language did you first learn to use?"
self.survey = AnonymousSurvey(question)
self.responses = ["C++", "Java", "Python"]
def test_OneResponse(self):
self.survey.storeResponse(self.responses[0])
self.assertIn(self.responses[0], self.survey.responses)
def test_ThreeResponses(self):
for response in self.responses:
self.survey.storeResponse(response)
for response in self.responses:
self.assertIn(response, self.survey.responses)
unittest.main()