• 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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的代码包含了两个函数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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    输出:

    PS D:\Unittest> python -m unittest a.py
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从输出可以看出,所有的测试用例都通过了,因为所有的断言都成功了。

    现在让我们尝试一个测试用例失败的情况。

    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    输出:

    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    从代码中可以看出,我们使用python -m unittest 来执行脚本。

    这段代码在未调用测试类的方法的情况下工作,因为unittest模块以特定的格式处理给定的脚本文件。

    由于我们的脚本包含TestCalc,unittest.TestCase的子类会被自动实例化。

    在实例化之后,会找到类内部的测试方法,并按顺序执行。要将方法视为测试方法,它必须以test_开头。

    找到测试方法后,它们按顺序调用;在我们的情况下,调用了test_CheckPrime和test_CheckFact两个方法。在我们的实现中,检查断言,并在出现意外行为时将错误抛出到输出中。

    从包含错误的测试用例中可以推断出,由于代码的编写方式,CalcFact方法中开始发生了无限递归,现在可以通过测试用例来修复它。

    如果你想知道为什么会发生错误,那是因为初始条件没有检查小于1的数字。

    以下是使用unittest的一些优点:

    • 包含在Python标准库中
    • 将相关的测试用例放入单个测试套件中
    • 测试集合速度快
    • 精确的测试时间持续时间

    unittest具有以下缺点:

    • 可能难以理解
    • 没有彩色输出
    • 可能太冗长

    Pytest框架的单元测试

    与unittest不同,Pytest不是一个内置模块,我们必须单独下载它。不过,安装Pytest相对简单;我们可以使用pip来执行以下命令:

    pip install pytest
    
    
    • 1
    • 2

    让我们使用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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出:

    ============================================================== 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 ===============================================================
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    现在,让我们来看一个失败的测试用例。

    ============================================================== 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 ========================================================== 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    使用 Pytest 编写的测试用例比 unittest 简单一点; 不用创建一个作为 unittest.TestCase 子类的类,我们可以在方法的开头使用 test_ 编写我们的测试函数。

    以下是在 Python 中使用 Pytest 框架的一些优点。

    • 紧凑的测试套件
    • 最小样板代码
    • 插件支持
    • 整洁和适当的输出呈现
    • 通常与其他框架不兼容

    推荐Python自动化测试学习视频

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

  • 相关阅读:
    FPGA与STM32_FSMC总线通信实验
    要做出高品质的docker镜像,一个靠谱的dockerfile必不可少!
    Bandizip去除广告,注册图标,状态栏文本
    如何判断mysql innodb buffer pool内存配置不足
    从小白到架构师(4): Feed 流系统实战
    Vue学习笔记(二)
    基于循环队列和信号量的生产和消费者模型
    云原生之Kubernetes:20、Flannel网络插件详解
    为什么训练交叉验证精度很高但是测试特别差劲
    92. 反转链表 II【链表】(链表内指定区间反转)
  • 原文地址:https://blog.csdn.net/2301_78042008/article/details/133089148