Python Unittest 和 Pytest
这篇文章的主要目的是讨论Python中两个最常用的单元测试框架unittest和pytest,它们的优缺点以及在何时选择哪个框架。
Python unittest vs Pytest
在编写任何软件时,我们必须在开发过程中保持错误检查的过程。这确保了一旦软件达到发布阶段,在使用过程中遇到的错误数量最小。
Python也有各种测试框架,可以通过给定不同的输入来测试编写的代码,以检查其行为。
如果遇到任何错误,可以在开发阶段进行修正,而不是在应用程序初始发布之后进行紧急修补。
示例代码:
class Calculate:
def CheckPrime(self, a):
for i in range(a):
if (a % i):
return False
return True
def CalcFact(self, a):
if (a == 1):
return a
else:
return a * self.fact(a-1)
上面的代码包含了两个函数CheckPrime和CalcFact,根据它们的名称可以看出它们检查素数和计算阶乘。
为了确保Calculate方法正常工作,必须检查通过给定不同输出产生的可能出现的错误。
那么,我们如何做到这一点?为了确保我们的代码没有错误,我们可以使用不同的测试框架编写测试用例,并在其上测试我们的代码以检查代码的完整性。
虽然有许多测试框架,但最常用的两个是unittest和pytest。让我们逐个探讨它们。
unittest框架的单元测试
unittest是Python标准库中包含的单元测试框架。该框架受到了JUnit的启发,JUnit是一个用于单元测试的Java框架。
在讨论unittest的工作原理之前,了解unittest中常用的术语是很重要的(也用于其他相关框架)。
- 测试用例(Test Case)- 最小的测试单位- 通常包含一个或多个
- 测试套件(Test Suite)- 组合的测试用例- 通常依次执行
- 测试运行器(Test Runner)- 协调和处理测试用例和测试套件的执行
由于Python的标准库已经包含unittest,因此无需下载任何外部模块即可开始使用unittest编写单元测试。
我们可以在导入unittest模块后开始。现在,让我们专注于之前的代码。
示例代码:
import unittest
class Calculate:
def CheckPrime(self, a):
for i in range(2, a):
if (a % i == 0):
return False
return True
def CalcFact(self, a):
if (a == 1):
return a
else:
return a * self.CalcFact(a-1)
class TestCalc(unittest.TestCase):
def test_CheckPrime(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CheckPrime(2), True)
self.assertEqual(calc.CheckPrime(3), True)
self.assertEqual(calc.CheckPrime(4), False)
self.assertEqual(calc.CheckPrime(80), False)
def test_CheckFact(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CalcFact(2), 2)
self.assertEqual(calc.CalcFact(3), 6)
输出:
PS D:\Unittest> python -m unittest a.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
从输出可以看出,所有的测试用例都通过了,因为所有的断言都成功了。
现在让我们尝试一个测试用例失败的情况。
def test_CheckFact(self):
calc = Calculate()
# Passing different outputs
self.assertEqual(calc.CalcFact(2), 2)
self.assertEqual(calc.CalcFact(3), 6)
# Supposed to throw an error
self.assertEqual(calc.CalcFact(0), 0)
输出:
PS D:\Unittest> python -m unittest a.py
======================================================================
ERROR: test_CheckFact (a.TestCalc)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\Python Articles\a.py", line 34, in test_CheckFact
self.assertEqual(calc.CalcFact(0), 0) # Supposed to throw an error
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
File "D:\Python Articles\a.py", line 15, in CalcFact
return a * self.CalcFact(a-1)
[The previous line is repeated 974 more times]
File "D:\Python Articles\a.py", line 12, in CalcFact
if (a == 1):
RecursionError: maximum recursion depth exceeded in comparison
----------------------------------------------------------------------
Ran 2 tests in 0.004s
FAILED (errors=1)
从代码中可以看出,我们使用python -m unittest <name_of_script.py>来执行脚本。
这段代码在未调用测试类的方法的情况下工作,因为unittest模块以特定的格式处理给定的脚本文件。
由于我们的脚本包含TestCalc,unittest.TestCase的子类会被自动实例化。
在实例化之后,会找到类内部的测试方法,并按顺序执行。要将方法视为测试方法,它必须以test_开头。
找到测试方法后,它们按顺序调用;在我们的情况下,调用了test_CheckPrime和test_CheckFact两个方法。在我们的实现中,检查断言,并在出现意外行为时将错误抛出到输出中。
从包含错误的测试用例中可以推断出,由于代码的编写方式,CalcFact方法中开始发生了无限递归,现在可以通过测试用例来修复它。
如果你想知道为什么会发生错误,那是因为初始条件没有检查小于1的数字。
以下是使用unittest的一些优点:
- 包含在Python标准库中
- 将相关的测试用例放入单个测试套件中
- 测试集合速度快
- 精确的测试时间持续时间
unittest具有以下缺点:
- 可能难以理解
- 没有彩色输出
- 可能太冗长
Pytest框架的单元测试
与unittest不同,Pytest不是一个内置模块,我们必须单独下载它。不过,安装Pytest相对简单;我们可以使用pip来执行以下命令:
pip install pytest
让我们使用Pytest编写一些测试用例。在开始之前,让我们看一下Pytest与unittest在编写测试用例方面的区别。对于使用Pytest编写的单元测试,我们必须:
- 创建一个单独的目录,并将要测试的脚本放在新创建的目录中。
- 在文件中编写测试,文件要么以test_开头,要么以_test.py结尾。例如,test_calc.py或calc_test.py。
考虑以下使用Pytest编写的代码。
def test_CheckPrime():
calc = Calculate()
# Passing different outputs
assert calc.CheckPrime(2) == True
assert calc.CheckPrime(3) == True
assert calc.CheckPrime(4) == False
assert calc.CheckPrime(80) == False
def test_CheckFact():
calc = Calculate()
# Passing different outputs
assert calc.CalcFact(2) == 2
assert calc.CalcFact(3) == 6
# assert calc.CalcFact(0) == 0 # Supposed to throw an error
输出:
============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items
test_a.py
[100%]
=============================================================== 2 passed in 0.04s ===============================================================
现在,让我们来看一个失败的测试用例。
============================================================== test session starts ==============================================================
platform win32 -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: D:\Unittest
collected 2 items
test_a.py .F
[100%]
=================================================================== FAILURES ====================================================================
________________________________________________________________ test_CheckFact _________________________________________________________________
def test_CheckFact():
calc = Calculate()
# Passing different outputs
assert calc.CalcFact(2) == 2
assert calc.CalcFact(3) == 6
> assert calc.CalcFact(0) == 0 # Supposed to throw an error
test_a.py:50:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
test_a.py:13: in CalcFact
return a * self.CalcFact(a-1)
.
.
.
.
.
RecursionError: maximum recursion depth exceeded in comparison
test_a.py:10: RecursionError
============================================================ short test summary info ============================================================
FAILED test_a.py::test_CheckFact - RecursionError: maximum recursion depth exceeded in comparison
========================================================== 1 failed, 1 passed in 2.42s ==========================================================
使用 Pytest 编写的测试用例比 unittest 简单一点; 不用创建一个作为 unittest.TestCase 子类的类,我们可以在方法的开头使用 test_ 编写我们的测试函数。
以下是在 Python 中使用 Pytest 框架的一些优点。
- 紧凑的测试套件
- 最小样板代码
- 插件支持
- 整洁和适当的输出呈现
- 通常与其他框架不兼容
相关文章
Python 模拟类属性
发布时间:2023/06/22 浏览次数:161 分类:Python
-
本文的主要目的是演示如何使用 python 单元测试模块 unittest 操作类属性以进行测试和调试。模拟类属性的原因
Python 单元测试设置
发布时间:2023/06/22 浏览次数:127 分类:Python
-
在本文中,我们将讨论什么是单元测试和 Python 单元测试框架。 我们还讨论了如何使用 setUp() 函数进行单元测试以及示例。
Python rsync 同步
发布时间:2023/06/22 浏览次数:129 分类:Python
-
本文将探讨 rsync 以及我们如何从 Python 脚本中使用它。Python同步如上所述,rsync 是一个强大的工具
Python Deque Peek 介绍
发布时间:2023/06/22 浏览次数:198 分类:Python
-
本文演示了如何在 Python 中查看/检查双端队列(双端队列)前面的元素,而无需将它们从双端队列中删除。Python Deque Peek 概述
Python 中的异步请求
发布时间:2023/06/22 浏览次数:74 分类:Python
-
今天我们来学习一下异步请求; 这个讨论将引出代码示例,看看我们如何在 Python 中编写异步请求。Python 中的异步请求
Python - 等待异步函数完成
发布时间:2023/06/22 浏览次数:163 分类:Python
-
本文介绍如何创建异步函数并使用await 关键字中断进程。 我们还将学习如何在 Python 中使用任务而不是线程。
在 Python 中列出虚拟环境
发布时间:2023/06/21 浏览次数:149 分类:Python
-
在本文中,我们将讨论什么是 Python 中的虚拟环境以及列出它们的一些命令。虚拟环境是一个独立的环境,我们可以在其中安装库、包、脚本和Python解释器。
Python 中的 Urljoin 简介
发布时间:2023/06/21 浏览次数:178 分类:Python
-
本篇文章介绍了如何使用 Python 中的 urljoin() 模块形成 URL,并介绍了使用该模块时的行为。
Python 中并发方面的差异
发布时间:2023/06/21 浏览次数:68 分类:Python
-
本文将讨论如何在Python中实现并发以及其优点和缺点。线程和多线程 线程在Python中已经存在很长时间了。