首页 > 后端开发 > Python教程 > python单元测试指南,并结成pytest

python单元测试指南,并结成pytest

William Shakespeare
发布: 2025-02-19 08:57:13
原创
824 人浏览过

A Guide to Python Unit Testing with unittest and pytest

本文探讨软件测试的意义以及为何你应该重视它。我们将学习如何设计单元测试以及如何编写 Python 单元测试。特别是,我们将探讨 Python 中两个最常用的单元测试框架:unittest 和 pytest。

关键要点

  • 单元测试是软件开发中至关重要的环节,它允许开发人员测试程序的特定组件或单元,以确保它们按预期运行。Python 中流行的单元测试框架包括 unittest 和 pytest。
  • 设计良好的单元测试应该快速、独立、可重复、可靠且命名恰当。“准备、执行、断言 (AAA)”模式通常用于组织单元测试,将设置、执行和验证分开。
  • unittest 框架是 Python 标准库的一部分,其灵感来自 Java 的单元测试框架 JUnit。它使用特殊的断言方法,并要求将测试编写为继承自 unittest.TestCase 类的类的方法。
  • pytest 框架允许使用更少的代码进行复杂的测试,支持 unittest 测试套件,并提供超过 800 个外部插件。与 unittest 不同,pytest 使用普通的 Python 断言方法,使其更简单、更直观。
  • 尽管单元测试有很多优点,但必须记住,测试只能证明缺陷的存在,而不能证明缺陷的缺失。即使所有测试都通过,也不能证明软件系统没有错误。

软件测试简介

软件测试是检查软件产品行为以评估和验证其是否符合规范的过程。软件产品可能包含数千行代码和数百个协同工作的组件。如果一行代码无法正常工作,则错误可能会传播并导致其他错误。因此,为了确保程序按预期运行,必须对其进行测试。

由于现代软件可能相当复杂,因此存在多个级别的测试来评估正确性的不同方面。根据 ISTQB 认证测试基础级别教学大纲,软件测试有四个级别:

  1. 单元测试:测试特定的代码行
  2. 集成测试:测试多个单元之间的集成
  3. 系统测试:测试整个系统
  4. 验收测试:检查是否符合业务目标

本文将讨论单元测试,但在深入探讨之前,我想介绍软件测试中一个重要的原则。

测试只能证明缺陷的存在,而不能证明缺陷的缺失。

ISTQB CTFL 教学大纲 2018

换句话说,即使你运行的所有测试都没有显示任何失败,也不能证明你的软件系统没有错误,或者另一个测试用例不会发现你软件行为中的缺陷。

什么是单元测试?

这是第一个测试级别,也称为组件测试。在此部分中,对单个软件组件进行测试。根据编程语言的不同,软件单元可能是类、函数或方法。例如,如果你有一个名为 ArithmeticOperations 的 Java 类,其中包含 multiply 和 divide 方法,则 ArithmeticOperations 类的单元测试需要测试 multiply 和 divide 方法的正确行为。

单元测试通常由软件测试人员执行。要运行单元测试,软件测试人员(或开发人员)需要访问源代码,因为源代码本身就是被测试的对象。因此,这种直接测试源代码的软件测试方法称为白盒测试。

你可能想知道为什么你应该担心软件测试,以及它是否值得。在下一节中,我们将分析测试软件系统背后的动机。

为什么你应该进行单元测试

软件测试的主要优点是它提高了软件质量。软件质量至关重要,尤其是在软件处理我们日常活动中各种各样的世界中。提高软件质量仍然是一个过于模糊的目标。让我们尝试更好地说明我们所说的软件质量。根据 ISO/IEC 标准 9126-1 ISO 9126,软件质量包括以下因素:

  • 可靠性
  • 功能性
  • 效率
  • 可用性
  • 可维护性
  • 可移植性

如果你拥有一家公司,那么你应该仔细考虑软件测试活动,因为它会影响你的业务。例如,2022 年 5 月,特斯拉召回了 130,000 辆汽车,原因是车辆信息娱乐系统出现问题。然后通过“空中”分发的软件更新解决了这个问题。这些故障给公司造成了时间和金钱损失,也给客户带来了问题,因为他们有一段时间无法使用他们的汽车。测试软件确实需要花钱,但公司也可以节省数百万的技术支持费用。

单元测试侧重于检查软件是否正确运行,这意味着检查输入和输出之间的映射是否都正确完成。作为低级别测试活动,单元测试有助于尽早识别错误,从而不会将其传播到软件系统的更高级别。

单元测试的其他优点包括:

  • 简化集成:通过确保所有组件都能单独正常工作,更容易解决集成问题。
  • 最大限度地减少代码回归:通过大量的测试用例,如果将来对源代码进行一些修改会导致问题,则更容易找到问题。
  • 提供文档:通过测试输入和输出之间的正确映射,单元测试提供了有关被测试方法或类的使用方法的文档。

设计测试策略

现在让我们看看如何设计测试策略。

测试范围的定义

在开始规划测试策略之前,有一个重要的问题需要回答。你想测试软件系统的哪些部分?

这是一个关键问题,因为穷举测试是不可能的。因此,你无法测试所有可能的输入和输出,但你应该根据所涉及的风险对测试进行优先级排序。

在定义测试范围时,需要考虑许多因素:

  • 风险:如果错误影响此组件,会产生什么业务后果?
  • 时间:你希望软件产品多久准备好?你有没有最后期限?
  • 预算:你愿意投资多少资金用于测试活动?

一旦你定义了测试范围(指定你应该测试什么以及不应该测试什么),你就可以讨论一个好的单元测试应该具备的特性了。

单元测试的特性

  • 快速。单元测试大多是自动执行的,这意味着它们必须快速。缓慢的单元测试更有可能被开发人员跳过,因为它们不会提供即时反馈。
  • 独立。单元测试根据定义是独立的。它们测试单个代码单元,并且不依赖于任何外部因素(例如文件或网络资源)。
  • 可重复。单元测试会重复执行,并且结果必须随时间保持一致。
  • 可靠。只有当被测系统中存在错误时,单元测试才会失败。环境或测试的执行顺序不应该重要。
  • 正确命名。测试的名称应提供有关测试本身的相关信息。

在深入研究 Python 中的单元测试之前,还有一个步骤缺失。我们如何组织测试以使它们干净易读?我们使用一种称为准备、执行和断言 (AAA) 的模式。

AAA 模式

准备、执行和断言 (AAA) 模式是用于编写和组织单元测试的常用策略。它的工作方式如下:

  • 在准备阶段,设置测试所需的所有对象和变量。
  • 接下来,在执行阶段,调用被测试的函数/方法/类。
  • 最后,在断言阶段,我们验证测试的结果。

此策略通过分离测试的所有主要部分(设置、执行和验证)提供了一种干净的方法来组织单元测试。此外,单元测试更容易阅读,因为它们都遵循相同的结构。

Python 中的单元测试:unittest 或 pytest?

我们现在将讨论 Python 中的两个不同的单元测试框架。这两个框架是 unittest 和 pytest。

unittest 简介

Python 标准库包含 unittest 单元测试框架。此框架的灵感来自 JUnit,它是 Java 中的单元测试框架。

正如官方文档中所述,unittest 支持我们将在这篇文章中提到的几个重要概念:

  • 测试用例,这是测试的单个单元
  • 测试套件,这是一组一起执行的测试用例
  • 测试运行器,这是处理所有测试用例的执行和结果的组件

unittest 有自己的编写测试的方法。特别是,我们需要:

  1. 将我们的测试编写为继承自 unittest.TestCase 类的类的方法
  2. 使用特殊的断言方法

由于 unittest 已经安装,我们准备编写我们的第一个单元测试!

使用 unittest 编写单元测试

假设我们有 BankAccount 类:

import unittest

class BankAccount:
  def __init__(self, id):
    self.id = id
    self.balance = 0

  def withdraw(self, amount):
    if self.balance >= amount:
      self.balance -= amount
      return True
    return False

  def deposit(self, amount):
    self.balance += amount
    return True
登录后复制
登录后复制

我们不能提取超过存款可用额度的钱,所以让我们测试一下我们的源代码是否正确处理这种情况。

在同一个 Python 文件中,我们可以添加以下代码:

class TestBankOperations(unittest.TestCase):
    def test_insufficient_deposit(self):
      # Arrange
      a = BankAccount(1)
      a.deposit(100)
      # Act
      outcome = a.withdraw(200)
      # Assert
      self.assertFalse(outcome)
登录后复制
登录后复制

我们正在创建一个名为 TestBankOperations 的类,它是 unittest.TestCase 的子类。通过这种方式,我们正在创建一个新的测试用例。

在这个类中,我们定义了一个单个测试函数,其方法以 test 开头。这很重要,因为每个测试方法都必须以单词 test 开头。

我们期望此测试方法返回 False,这意味着操作失败了。为了断言结果,我们使用一个名为 assertFalse() 的特殊断言方法。

我们准备执行测试了。让我们在命令行上运行此命令:

python -m unittest example.py
登录后复制
登录后复制

这里,example.py 是包含所有源代码的文件的名称。输出应该如下所示:

<code>.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK</code>
登录后复制
登录后复制

很好!这意味着我们的测试成功了。现在让我们看看当出现故障时输出是什么样的。我们将一个新的测试添加到之前的类中。让我们尝试存入负数金额,这当然是不可能的。我们的代码会处理这种情况吗?

这是我们的新测试方法:

  def test_negative_deposit(self):
    # Arrange
    a = BankAccount(1)
    # Act
    outcome = a.deposit(-100)
    # Assert
    self.assertFalse(outcome)
登录后复制
登录后复制

我们可以使用 unittest 的详细模式来执行此测试,方法是使用 -v 标志:

python -m unittest -v example.py
登录后复制
登录后复制

现在的输出不同了:

<code>test_insufficient_deposit (example.TestBankOperations) ... ok
test_negative_deposit (example.TestBankOperations) ... FAIL

======================================================================
FAIL: test_negative_deposit (example.TestBankOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "example.py", line 35, in test_negative_deposit
    self.assertFalse(outcome)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)</code>
登录后复制
登录后复制

在这种情况下,详细标志为我们提供了更多信息。我们知道 test_negative_deposit 失败了。特别是,AssertionError 告诉我们,预期结果应该是 false,但 True 不是 false,这意味着该方法返回了 True。

unittest 框架根据我们的需求提供不同的断言方法:

  • assertEqual(x,y),测试 x == y 是否成立
  • assertRaises(exception_type),检查是否引发了特定异常
  • assertIsNone(x),测试 x 是否为 None
  • assertIn(x,y),测试 x 是否在 y 中

现在我们已经基本了解了如何使用 unittest 框架编写单元测试,让我们来看看另一个名为 pytest 的 Python 框架。

pytest 简介

pytest 框架是一个 Python 单元测试框架,它具有一些相关的特性:

  • 它允许使用更少的代码进行复杂的测试
  • 它支持 unittest 测试套件
  • 它提供超过 800 个外部插件

由于 pytest 默认情况下未安装,因此我们必须先安装它。请注意,pytest 需要 Python 3.7 。

安装 pytest

安装 pytest 非常容易。你只需要运行以下命令:

import unittest

class BankAccount:
  def __init__(self, id):
    self.id = id
    self.balance = 0

  def withdraw(self, amount):
    if self.balance >= amount:
      self.balance -= amount
      return True
    return False

  def deposit(self, amount):
    self.balance += amount
    return True
登录后复制
登录后复制

然后通过键入以下内容检查一切是否已正确安装:

class TestBankOperations(unittest.TestCase):
    def test_insufficient_deposit(self):
      # Arrange
      a = BankAccount(1)
      a.deposit(100)
      # Act
      outcome = a.withdraw(200)
      # Assert
      self.assertFalse(outcome)
登录后复制
登录后复制

输出应该如下所示:

python -m unittest example.py
登录后复制
登录后复制

很好!让我们使用 pytest 编写第一个测试。

使用 pytest 编写单元测试

我们将使用之前编写的 BankAccount 类,我们将测试与之前相同的 method。通过这种方式,更容易比较使用这两个框架编写测试所需的努力。

要使用 pytest 进行测试,我们需要:

  • 创建一个目录并将我们的测试文件放入其中。
  • 在名称以 test_ 开头或以 _test.py 结尾的文件中编写我们的测试。pytest 将在当前目录及其子目录中查找这些文件。

因此,我们创建一个名为 test_bank.py 的文件并将其放入文件夹中。这是我们的第一个测试函数的样子:

<code>.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK</code>
登录后复制
登录后复制

正如你所注意到的,与 unittest 版本相比,唯一改变的是 assert 部分。在这里,我们使用普通的 Python 断言方法。

现在我们可以看看 test_bank.py 文件:

  def test_negative_deposit(self):
    # Arrange
    a = BankAccount(1)
    # Act
    outcome = a.deposit(-100)
    # Assert
    self.assertFalse(outcome)
登录后复制
登录后复制

要运行此测试,让我们在包含 test_bank.py 文件的文件夹中打开一个命令提示符。然后,运行以下命令:

python -m unittest -v example.py
登录后复制
登录后复制

输出将如下所示:

<code>test_insufficient_deposit (example.TestBankOperations) ... ok
test_negative_deposit (example.TestBankOperations) ... FAIL

======================================================================
FAIL: test_negative_deposit (example.TestBankOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "example.py", line 35, in test_negative_deposit
    self.assertFalse(outcome)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)</code>
登录后复制
登录后复制

在这种情况下,我们可以看到编写和执行测试是多么容易。此外,我们可以看到与 unittest 相比,我们编写的代码更少。测试的结果也很容易理解。

让我们继续看看失败的测试!

我们使用之前编写的第二个方法,它被称为 test_negative_deposit。我们重构 assert 部分,结果如下:

pip install -U pytest
登录后复制

我们像以前一样运行测试,这应该是输出:

pytest --version
登录后复制

通过解析输出,我们可以读取 collected 2 items,这意味着已经执行了两个测试。向下滚动,我们可以看到在测试 test_negative_deposit 方法时发生了一个错误。特别是,错误发生在评估断言时。此外,报告还指出 outcome 变量的值为 True,这意味着 deposit 方法包含错误。

由于 pytest 使用默认的 Python 断言关键字,我们可以将获得的任何输出与存储预期结果的另一个变量进行比较。所有这些都不需要使用特殊的断言方法。

结论

总而言之,在本文中,我们介绍了软件测试的基础知识。我们发现了为什么软件测试至关重要以及为什么每个人都应该测试他们的代码。我们讨论了单元测试以及如何在 Python 中设计和实现简单的单元测试。

我们使用了两个名为 unittest 和 pytest 的 Python 框架。两者都具有有用的功能,并且它们是 Python 单元测试中最常用的两个框架。

最后,我们看到了两个基本的测试用例,以让你了解如何按照准备、执行和断言模式编写测试。

我希望我已经说服你软件测试的重要性。选择一个框架,例如 unittest 或 pytest,然后开始测试——因为额外的努力是值得的!

如果你喜欢这篇文章,你可能还会发现以下内容有用:

  • Cypress 测试:运行 Web 应用程序测试指南
  • 如何使用 Jest 测试 React 组件
  • 使用 Puppeteer 学习端到端测试
  • 免提持续测试的 3 种方法
  • 重新介绍 Jenkins:使用管道进行自动化测试

关于 Python 单元测试的常见问题

什么是 Python 中的单元测试?Python 中的单元测试是一种软件测试技术,其中程序的单个单元或组件被隔离测试,以确保每个单元都能按预期工作。

为什么单元测试在 Python 开发中很重要?单元测试有助于确保 Python 程序中各个组件的正确性。它有助于尽早发现错误,为代码更改提供安全网,并支持代码的可维护性。

如何在 Python 中编写单元测试?Python 中的单元测试通常使用内置的 unittest 模块编写。你创建继承自 unittest.TestCase 的测试类,并在这些类中编写测试方法。测试方法通常以“test”开头。

除了 unittest,我还能使用哪些其他框架进行 Python 单元测试?是的,除了 unittest 之外,还有其他流行的 Python 测试框架,例如 pytest 和 nose2。这些框架提供了不同的功能和语法,使开发人员可以选择最适合其需求的框架。

Python 单元测试中的 fixture 的作用是什么?Fixture 是一种在 Python 中设置前提条件并在测试后清理的方法。它们有助于确保测试是独立的并且可以独立运行。

什么是测试覆盖率,为什么它很重要?测试覆盖率衡量的是你的测试所执行的代码库的百分比。它有助于识别未经测试的代码,并确保你的测试是全面的,从而减少发现错误的可能性。

在 Python 中编写有效的单元测试有哪些最佳实践?是的,一些最佳实践包括编写独立和隔离的测试,使用描述性的测试方法名称以及测试边界情况。此外,要努力获得良好的测试覆盖率并经常运行测试。

以上是python单元测试指南,并结成pytest的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板