Python中两个最常用的单元测试框架unittest和pytest

2023-09-20 17:15:14

这篇文章的主要目的是讨论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自动化测试学习视频

软件测试黄胖子
软件测试面试官黄财财

更多推荐

【直播预约中】 腾讯大数据 x StarRocks|构建新一代实时湖仓

随着信息时代的兴起,数据已成为推动业务决策和创新的核心要素;结构化、半结构化等多种类型的数据呈现爆炸式增长,如何高效处理和分析海量数据已经成为关键挑战,结合传统数仓与数据湖优势的湖仓一体(Lakehouse)架构崭露头角,成为大数据领域势不可挡的趋势;基于湖仓一体架构,企业可以基于一份数据,满足BI报表、交互式数据探查

R语言STAN贝叶斯线性回归模型分析气候变化影响北半球海冰范围和可视化检查模型收敛性...

原文链接:http://tecdat.cn/?p=24334像任何统计建模一样,贝叶斯建模可能需要为你的研究问题设计合适的模型,然后开发该模型,使其符合你的数据假设并运行(点击文末“阅读原文”获取完整代码数据)。相关视频1.了解Stan统计模型可以在R或其他统计语言的各种包中进行拟合。但有时你在概念上可以设计的完美模型

Vue3为什么推荐使用ref而不是reactive

为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替代,可以存放任何数据类型,而reactive声明的数据类型只能是对象;先抛出结论,再详细说原因:非必要不用reactive

Vue3.3 新特性 - 初体验

前言Vue3.3是Vue.js框架的最新版本,它带来了一些令人兴奋的新特性和改进。本文将对一些重要的新特性进行简要介绍,并通过示例代码进行说明。CompositionAPIVue3.3引入了CompositionAPI,这是一个全新的API风格,旨在提供更好的代码组织和可重用性。与之前的OptionsAPI相比,Com

BabelEdit 5.0.1 Crack

BabelEdit加强软件本地化。BabelEdit是处理json、yaml、php、arb、vue、properties、resx或xliff翻译文件的可靠解决方案。旨在使开发过程更加简化和高效。下载BabelEdit5.0.0对于Windows也适用于macOS和LinuxBabelEdit-适用于Web和应用程序

React核心概念

JSX基础语法在React中,使用JSX来描述页面。使用JSX来描述页面时,有如下的一些语法规则:根元素只能有一个JSX中使用JavaScript表达式。表达式在花括号{}内属性值指定为字符串字面量,或者在属性值插入一个JavaScript表达式style对应样式对象,class要写作className注释需要写在花括

gin 基本使用

gin初体验import("net/http""github.com/gin-gonic/gin")funcmain(){r:=gin.Default()r.GET("/ping",func(c*gin.Context){c.JSON(http.StatusOK,gin.H{"message":"pong",})})r

ESP32C3 PWM输出

目前对于遥控双发差速小飞机计划采用如下架构:ESP32C3做主控,兼具遥控收发和飞行控制锂电池供电,带电量检测双发,720空心杯电机,55mm桨,带电流检测MPU6050加速度计和陀螺仪预留4个控制信号输出马达控制要用到pwm,今天把esp32c3的pwm跑一下。简介esp32c3中把pwm外设称为“LEDPWM控制器

Qt重写QTreeWidget实现拖拽

介绍此文章记录QTreeWidget的重写进度,暂时停滞使用,重写了QTreeWidget的拖拽功能,和绘制功能,自定义了数据结构,增加复制,粘贴,删除,准备实现动态刷新数据支持千万数据动态刷新,重写了部分代码,重写了滑块拖拽但是有bug。效果展示实现功能实现了自定义节点类来存储数据。item采用Label来实现富文本

C++数据结构题:DS 顺序表--连续操作

建立顺序表的类,属性包括:数组、实际长度、最大长度(设定为1000)该类具有以下成员函数:构造函数:实现顺序表的初始化。插入多个数据的multiinsert(inti,intn,intitem[])函数,实现在第i个位置,连续插入来自数组item的n个数据,即从位置i开始插入多个数据。删除多个数据的multidel(i

JAVAEE初阶相关内容第十二弹--多线程(进阶)

目录一、JUC的常见类1、Callable接口1.1callable与runnable1.2代码实例(1)不使用Callable实现(2)使用Callable实现1.3理解Callable1.4理解FutureTask2、ReentrantLock2.1ReentrantLock的用法2.2ReentrantLock优

热文推荐