Unittest 和 Doctest 的区别
本教程将介绍 Python 中测试代码的两个主要框架:Doctest 和 Unittest,并讨论它们之间的区别。测试是软件开发的关键阶段,确保代码质量、可重用性和敏捷性。通过使用各种测试用例对代码库进行测试,可以避免代码破坏并最小化漏洞暴露。Python 提供了两个主要的测试框架:Doctest 和 Unittest,下面我们将探讨这两个框架。
Doctest 简介
Doctest 模块是内置的,这意味着它随 Python 安装而来,无需单独安装。Doctest 模块用于识别和执行类似交互式 Python 会话的代码片段,并测试以确保输出与所描述的期望输出匹配。Doctest 最常用于测试文档。它通过检查文档字符串中的语句序列,重新执行提取出的命令,并将其与文档字符串中的命令行输入进行比较。默认情况下,在使用 Doctest 模块时,当测试用例通过时不显示输出。但是,可以使用 Doctest 运行程序中的选项修改此行为。此外,Doctest 与 Python Unittest 框架的兼容性,使我们能够将 Doctest 作为常规 Unittest 测试用例执行。如果您想了解更多关于 Doctest 模块的信息,可以参考我们的 Python Doctest 模块 教程。
Unittest 简介
Unittest 框架提供了额外的选项,在执行测试用例时生成统计报告,包括通过和失败的测试用例数量。在 Unittest 中,类中创建的方法管理测试。它支持在测试时进行设置、自动化和关闭代码。它具有各种内置的丰富功能,包括生成器和组装管理器,这些功能不包含在 Doctest 中。
由于 Unittest 遵循面向对象的方法,因此它更适合在非生产环境中测试基于类的方法。另一方面,持续交付工具,如 Jenkins 或 Travis CI,则更适合生产环境。在接下来的部分中,我们将演示处理员工信息及其薪资上的 efs 的实际代码示例,并使用 Doctest 和 Unittest 进行测试。在进行测试后,我们将评估结果并提出进一步增强测试过程的建议。
编码示例 – 使用 Unittest
让我们实现 Employee 类并创建一些方法来使用 Unittest 进行测试。
以下是示例代码:
class Employee:
"""A sample employee class"""
epf = 0.95
def __init__(self, first_name, last_name, salary):
self.first_name = first_name
self.last_name = last_name
self.salary = salary
@property
def employee_mail(self):
return f'{self.first_name}.{self.last_name}@email.com'
@property
def employee_fullname(self):
return f'{self.first_name} {self.last_name}'
def apply_epf(self):
self.salary = int(self.salary * self.epf)
在上面的代码中,我们从 Python 包中导入 unittest,并导入 Employee 类。在 TestEmployee 类中,我们使用了 unittest 中的 “TestCase”,它允许我们验证每组输入的特定输出。然后,我们使用以 “test” 前缀开头的方法设计了三个不同的测试。在测试运行器中,”test” 前缀表示标识要作为测试运行的方法。
test_employee.py
import unittest
from employee import Employee
class TestEmployee(unittest.TestCase):
def setUp(self):
self.employee_1 = Employee('Peter', 'Parker', 500000)
self.employee_2 = Employee('Tony', 'Stark', 300000)
def test_employee_mail(self):
self.assertEqual(self.employee_1.employee_mail, 'Peter.Parker@email.com')
self.assertEqual(self.employee_2.employee_mail, 'Tony.Stark@email.com')
def test_employee_fullname(self):
self.assertEqual(self.employee_1.employee_fullname, 'Peter Parker')
self.assertEqual(self.employee_2.employee_fullname, 'Tony Stark')
def test_apply_epf(self):
self.employee_1.apply_epf()
self.employee_2.apply_epf()
self.assertEqual(self.employee_1.salary, 47500)
self.assertEqual(self.employee_2.salary, 28500)
if __name__ == '__main__':
unittest.main()
在上面的代码中,我们从 Python 包中导入 unittest,并导入 Employee 类。在 TestEmployee 类中,我们使用了 unittest 中的 “TestCase”,它提供了验证每组输入的特定输出的能力。然后,我们使用以 “test” 前缀开头的方法设计了三个不同的测试。在测试运行器中,”test” 前缀表示标识要作为测试运行的方法。
setUp() 方法在任何其他测试方法之前执行,在此方法中创建了客户端的实例。我们使用 assert() 方法中的 assertEqual() 方法,在每个测试方法中比较预期结果和实际结果。运行上述测试时,我们会得到以下结果。
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
如果我们更改预期输出,我们将看到以下结果。
C:\Users\User\Desktop\my_project\unittest>python test_employee.py
F..
======================================================================
FAIL: test_apply_epf (__main__.TestEmployee)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_employee.py", line 22, in test_apply_epf
self.assertEqual(self.employee_1.salary, 47500)
AssertionError: 475000 != 47500
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
这种测试方法可以快速识别错误并帮助确定代码中错误发生的位置。通过利用 unittest 并创建涵盖所有可能情况的测试,我们可以增强程序的清晰度、功能和逻辑流程。
使用 Doctest
与 unittest 相比,使用 doctest 更简单且需要的步骤更少。尽管它易于使用,但在使用 doctest 时需要谨慎,因为它具有某些限制。
现在,让我们演示如何使用 doctest 来说明之前的示例。
示例 –
class Employee:
"""A sample employee class"""
epf = 0.95
def __init__(self, first_name, last_name, salary):
self.first_name = first_name
self.last_name = last_name
self.salary = salary
@property
def employee_mail(self):
return f'{self.first_name}.{self.last_name}@email.com'
@property
def employee_fullname(self):
return f'{self.first_name} {self.last_name}'
def apply_epf(self):
self.salary = int(self.salary * self.epf)
现在,我们将在终端中执行以下操作。
class Employee:
"""A sample employee class
>>> employee_1 = Employee("John", "Brad", 500000)
>>> employee_2 = Employee("Tina", "Smith", 300000)
>>> employee_1.employee_mail()
'John.Brad@email.com'
>>> employee_2.employee_mail()
'Tina.Smith@email.com'
>>> employee_1.employee_fullname()
'John Brad'
>>> employee_2.employee_fullname()
'Tina Smith'
>>> employee_1.apply_epf()
4750
>>> employee_2.apply_epf()
2850
"""
epf = 0.95
def __init__(self, first_name, last_name, salary):
self.first_name = first_name
self.last_name = last_name
self.salary = salary
@property
def employee_mail(self):
return f'{self.first_name}.{self.last_name}@email.com'
@property
def employee_fullname(self):
return f'{self.first_name} {self.last_name}'
def apply_epf(self):
self.salary = int(self.salary * self.epf)
我们在终端中运行 python doctest_employee -v 命令,它将返回以下输出。
输出 –
Trying:
employee_1 = Employee("John", "Brad", 5000)
Expecting nothing
ok
Trying:
employee_2 = Employee("Tina", "Smith", 3000)
Expecting nothing
ok
Trying:
employee_1.employee_mail()
Expecting:
'stash'
ok
Trying:
employee_2.employee_mail()
Expecting:
'stash'
ok
Trying:
employee_1.employee_fullname()
Expecting:
'John Brad'
ok
Trying:
employee_2.employee_fullname()
Expecting:
'Tina Smith'
ok
Trying:
employee_1.apply_epf()
Expecting:
4750
ok
Trying:
employee_2.apply_epf()
Expecting:
2850
ok
5 items had no tests:
__main__
__main__.employee.__init__
__main__.employee.apply_epf
__main__.employee.employee_fullname
__main__.employee.employee_mail
1 items passed all tests:
8 tests in __main__.employee
8 tests in 6 items.
8 passed and 0 failed.
Test passed.
如上面的输出所示,所有测试都已通过。Doctest 使编写可执行文档更加简单且更适合于包的文档编写,而 unittest 更适合于测试文档。
结论
本教程介绍了 doctest 和 unittest 之间的区别。unittest 框架提供了一种面向对象的方法来测试代码,包括自动化、设置和撤销测试代码等功能。另一方面,doctest 更适用于文档,因为它可以嵌入在代码的文档字符串中。