作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。
介绍
Python 标准库包含unittest
帮助您为 Python 代码编写和运行测试的模块。
使用该unittest
模块编写的测试可以帮助您找到程序中的错误,并防止随着时间的推移更改代码时发生回归。坚持测试驱动开发的团队可能会发现unittest
确保所有编写的代码都有一组相应的测试很有用。
在本教程中,您将使用 Python 的unittest
模块为函数编写测试。
先决条件
要充分利用本教程,您需要:
- 理解 Python 中的函数。您可以查看How To Define Functions in Python 3教程,它是How To Code in Python 3系列的一部分。
定义TestCase
子类
该unittest
模块提供的最重要的类之一名为TestCase
. TestCase
提供用于测试我们的功能的通用脚手架。让我们考虑一个例子:
import unittest
def add_fish_to_aquarium(fish_list):
if len(fish_list) > 10:
raise ValueError("A maximum of 10 fish can be added to the aquarium")
return {"tank_a": fish_list}
class TestAddFishToAquarium(unittest.TestCase):
def test_add_fish_to_aquarium_success(self):
actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])
expected = {"tank_a": ["shark", "tuna"]}
self.assertEqual(actual, expected)
首先,我们导入unittest
以使模块可用于我们的代码。然后我们定义我们想要测试的函数——这里是add_fish_to_aquarium
。
在这种情况下,我们的add_fish_to_aquarium
函数接受名为 的鱼列表,fish_list
如果fish_list
元素超过 10 个,则会引发错误。然后该函数返回一个字典,将鱼缸的名称映射"tank_a"
到给定的fish_list
.
命名的类TestAddFishToAquarium
被定义为 的子类unittest.TestCase
。在test_add_fish_to_aquarium_success
上定义了一个命名方法TestAddFishToAquarium
。使用特定输入test_add_fish_to_aquarium_success
调用add_fish_to_aquarium
函数并验证实际返回值是否与我们期望返回的值匹配。
现在我们已经定义了一个TestCase
带有测试的子类,让我们回顾一下如何执行该测试。
执行一个 TestCase
在上一节中,我们创建了一个TestCase
名为的子类TestAddFishToAquarium
。从与test_add_fish_to_aquarium.py
文件相同的目录中,让我们使用以下命令运行该测试:
- python -m unittest test_add_fish_to_aquarium.py
我们调用名为Python库模块unittest
用python -m unittest
。然后,我们提供了包含我们的文件的路径TestAddFishToAquarium
TestCase
作为参数。
运行此命令后,我们会收到如下输出:
Output.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
该unittest
模块运行了我们的测试并告诉我们我们的测试运行了OK
。.
输出第一行的单曲代表我们通过的测试。
注意: TestCase
将测试方法识别为任何以 开头的方法test
。例如,def test_add_fish_to_aquarium_success(self)
被识别为测试并将照此运行。def example_test(self)
相反,不会被识别为测试,因为它不以 开头test
。只有以 开头的方法test
才会在您运行时运行并报告python -m unittest ...
。
现在让我们尝试一个失败的测试。
我们在测试方法中修改以下突出显示的行以引入失败:
import unittest
def add_fish_to_aquarium(fish_list):
if len(fish_list) > 10:
raise ValueError("A maximum of 10 fish can be added to the aquarium")
return {"tank_a": fish_list}
class TestAddFishToAquarium(unittest.TestCase):
def test_add_fish_to_aquarium_success(self):
actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])
expected = {"tank_a": ["rabbit"]}
self.assertEqual(actual, expected)
修改后的测试将失败,因为add_fish_to_aquarium
不会"rabbit"
在其属于 的鱼列表中返回"tank_a"
。让我们运行测试。
再次,从test_add_fish_to_aquarium.py
我们运行的同一目录:
- python -m unittest test_add_fish_to_aquarium.py
当我们运行此命令时,我们会收到如下输出:
OutputF
======================================================================
FAIL: test_add_fish_to_aquarium_success (test_add_fish_to_aquarium.TestAddFishToAquarium)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_add_fish_to_aquarium.py", line 13, in test_add_fish_to_aquarium_success
self.assertEqual(actual, expected)
AssertionError: {'tank_a': ['shark', 'tuna']} != {'tank_a': ['rabbit']}
- {'tank_a': ['shark', 'tuna']}
+ {'tank_a': ['rabbit']}
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
失败输出表明我们的测试失败。的实际输出与{'tank_a': ['shark', 'tuna']}
我们添加到的(不正确的)期望不匹配test_add_fish_to_aquarium.py
:{'tank_a': ['rabbit']}
。另请注意,.
输出的第一行现在有一个,而不是 a F
。而.
当测试通过字符被输出,F
是输出时unittest
运行一个测试失败。
现在我们已经编写并运行了一个测试,让我们尝试为add_fish_to_aquarium
函数的不同行为编写另一个测试。
测试引发异常的函数
unittest
还可以帮助我们验证如果输入太多鱼,该add_fish_to_aquarium
函数是否会引发ValueError
异常。让我们扩展我们之前的示例,并添加一个名为 的新测试方法test_add_fish_to_aquarium_exception
:
import unittest
def add_fish_to_aquarium(fish_list):
if len(fish_list) > 10:
raise ValueError("A maximum of 10 fish can be added to the aquarium")
return {"tank_a": fish_list}
class TestAddFishToAquarium(unittest.TestCase):
def test_add_fish_to_aquarium_success(self):
actual = add_fish_to_aquarium(fish_list=["shark", "tuna"])
expected = {"tank_a": ["shark", "tuna"]}
self.assertEqual(actual, expected)
def test_add_fish_to_aquarium_exception(self):
too_many_fish = ["shark"] * 25
with self.assertRaises(ValueError) as exception_context:
add_fish_to_aquarium(fish_list=too_many_fish)
self.assertEqual(
str(exception_context.exception),
"A maximum of 10 fish can be added to the aquarium"
)
新的测试方法test_add_fish_to_aquarium_exception
也调用该add_fish_to_aquarium
函数,但它使用包含"shark"
重复 25 次的字符串的 25 个元素长列表来调用。
test_add_fish_to_aquarium_exception
使用由提供的with self.assertRaises(...)
上下文管理器TestCase
来检查add_fish_to_aquarium
拒绝输入的列表太长。的第一个参数self.assertRaises
是我们期望引发的 Exception 类——在本例中,ValueError
. 该self.assertRaises
上下文管理被绑定到一个指定的变量exception_context
。该exception
属性上exception_context
包含底层ValueError
是add_fish_to_aquarium
提高。当我们调用str()
它ValueError
来检索它的消息时,它返回我们期望的正确异常消息。
从与 相同的目录中test_add_fish_to_aquarium.py
,让我们运行我们的测试:
- python -m unittest test_add_fish_to_aquarium.py
当我们运行此命令时,我们会收到如下输出:
Output..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
值得注意的是,如果add_fish_to_aquarium
没有引发异常或引发不同的异常(例如TypeError
代替ValueError
),我们的测试就会失败。
注: unittest.TestCase
包含了许多超越其他方法assertEqual
,并assertRaises
可以使用。断言方法的完整列表可以在文档中找到,但这里包含了一个选择:
方法 | 断言 |
---|---|
assertEqual(a, b) |
a == b |
assertNotEqual(a, b) |
a != b |
assertTrue(a) |
bool(a) is True |
assertFalse(a) |
bool(a) is False |
assertIsNone(a) |
a is None |
assertIsNotNone(a) |
a is not None |
assertIn(a, b) |
a in b |
assertNotIn(a, b) |
a not in b |
现在我们已经编写了一些基本的测试,让我们看看我们如何使用提供的其他工具TestCase
来利用我们正在测试的任何代码。
使用setUp
方法创建资源
TestCase
还支持一种setUp
方法来帮助您在每个测试的基础上创建资源。setUp
当您有一组通用的准备代码要在每个测试之前运行时,方法会很有帮助。setUp
让您将所有这些准备代码放在一个地方,而不是为每个单独的测试一遍又一遍地重复。
我们来看一个例子:
import unittest
class FishTank:
def __init__(self):
self.has_water = False
def fill_with_water(self):
self.has_water = True
class TestFishTank(unittest.TestCase):
def setUp(self):
self.fish_tank = FishTank()
def test_fish_tank_empty_by_default(self):
self.assertFalse(self.fish_tank.has_water)
def test_fish_tank_can_be_filled(self):
self.fish_tank.fill_with_water()
self.assertTrue(self.fish_tank.has_water)
test_fish_tank.py
定义一个名为FishTank
. FishTank.has_water
最初设置为False
,但可以True
通过调用设置为FishTank.fill_with_water()
。该TestCase
子类TestFishTank
定义了一个名为方法setUp
实例化一个新的FishTank
实例,并转让该实例self.fish_tank
。
由于setUp
在每个单独的测试方法之前运行,FishTank
因此为test_fish_tank_empty_by_default
和实例化了一个新实例test_fish_tank_can_be_filled
。test_fish_tank_empty_by_default
验证has_water
以False
. 调用 后test_fish_tank_can_be_filled
验证has_water
设置为。True
fill_with_water()
从与 相同的目录中test_fish_tank.py
,我们可以运行:
- python -m unittest test_fish_tank.py
如果我们运行前面的命令,我们将收到以下输出:
Output..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
最终输出显示两个测试都通过了。
setUp
允许我们编写为TestCase
子类中的所有测试运行的准备代码。
注意:如果您有多个带有TestCase
子类的测试文件要运行,请考虑使用python -m unittest discover
运行多个测试文件。运行python -m unittest discover --help
以获取更多信息。
使用tearDown
方法清理资源
TestCase
支持setUp
名为的方法的对应物tearDown
。tearDown
例如,如果我们需要清理与数据库的连接,或者在每次测试完成后对文件系统进行修改,这将非常有用。我们将回顾一个tearDown
与文件系统一起使用的示例:
import os
import unittest
class AdvancedFishTank:
def __init__(self):
self.fish_tank_file_name = "fish_tank.txt"
default_contents = "shark, tuna"
with open(self.fish_tank_file_name, "w") as f:
f.write(default_contents)
def empty_tank(self):
os.remove(self.fish_tank_file_name)
class TestAdvancedFishTank(unittest.TestCase):
def setUp(self):
self.fish_tank = AdvancedFishTank()
def tearDown(self):
self.fish_tank.empty_tank()
def test_fish_tank_writes_file(self):
with open(self.fish_tank.fish_tank_file_name) as f:
contents = f.read()
self.assertEqual(contents, "shark, tuna")
test_advanced_fish_tank.py
定义一个名为AdvancedFishTank
. AdvancedFishTank
创建一个名为的文件并将fish_tank.txt
字符串写入"shark, tuna"
其中。AdvancedFishTank
还公开了一个empty_tank
删除fish_tank.txt
文件的方法。的TestAdvancedFishTank
TestCase
子类定义两者setUp
和tearDown
方法。
该setUp
方法创建一个AdvancedFishTank
实例并将其分配给self.fish_tank
。该tearDown
方法调用empty_tank
方法 on self.fish_tank
:这确保fish_tank.txt
在每个测试方法运行后删除文件。这样,每个测试都从一个干净的石板开始。该test_fish_tank_writes_file
方法验证 的默认内容是否"shark, tuna"
已写入fish_tank.txt
文件。
从与test_advanced_fish_tank.py
让我们运行相同的目录中:
- python -m unittest test_advanced_fish_tank.py
我们将收到以下输出:
Output.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
tearDown
允许您编写为TestCase
子类中的所有测试运行的清理代码。
结论
在本教程中,您编写了TestCase
具有不同断言的类,使用了setUp
和tearDown
方法,并从命令行运行了测试。
该unittest
模块公开了您在本教程中未涵盖的其他类和实用程序。现在,你有一个底线,你可以使用该unittest
模块的文档,详细了解其他可用的类和公用事业。您可能还对如何将单元测试添加到您的 Django 项目感兴趣。