作者选择了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设置为。Truefill_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 项目感兴趣。