本文探讨软件测试的意义以及为何你应该重视它。我们将学习如何设计单元测试以及如何编写 Python 单元测试。特别是,我们将探讨 Python 中两个最常用的单元测试框架:unittest 和 pytest。
关键要点
软件测试简介
软件测试是检查软件产品行为以评估和验证其是否符合规范的过程。软件产品可能包含数千行代码和数百个协同工作的组件。如果一行代码无法正常工作,则错误可能会传播并导致其他错误。因此,为了确保程序按预期运行,必须对其进行测试。
由于现代软件可能相当复杂,因此存在多个级别的测试来评估正确性的不同方面。根据 ISTQB 认证测试基础级别教学大纲,软件测试有四个级别:
本文将讨论单元测试,但在深入探讨之前,我想介绍软件测试中一个重要的原则。
测试只能证明缺陷的存在,而不能证明缺陷的缺失。
— ISTQB CTFL 教学大纲 2018
换句话说,即使你运行的所有测试都没有显示任何失败,也不能证明你的软件系统没有错误,或者另一个测试用例不会发现你软件行为中的缺陷。
这是第一个测试级别,也称为组件测试。在此部分中,对单个软件组件进行测试。根据编程语言的不同,软件单元可能是类、函数或方法。例如,如果你有一个名为 ArithmeticOperations 的 Java 类,其中包含 multiply 和 divide 方法,则 ArithmeticOperations 类的单元测试需要测试 multiply 和 divide 方法的正确行为。
单元测试通常由软件测试人员执行。要运行单元测试,软件测试人员(或开发人员)需要访问源代码,因为源代码本身就是被测试的对象。因此,这种直接测试源代码的软件测试方法称为白盒测试。
你可能想知道为什么你应该担心软件测试,以及它是否值得。在下一节中,我们将分析测试软件系统背后的动机。
软件测试的主要优点是它提高了软件质量。软件质量至关重要,尤其是在软件处理我们日常活动中各种各样的世界中。提高软件质量仍然是一个过于模糊的目标。让我们尝试更好地说明我们所说的软件质量。根据 ISO/IEC 标准 9126-1 ISO 9126,软件质量包括以下因素:
如果你拥有一家公司,那么你应该仔细考虑软件测试活动,因为它会影响你的业务。例如,2022 年 5 月,特斯拉召回了 130,000 辆汽车,原因是车辆信息娱乐系统出现问题。然后通过“空中”分发的软件更新解决了这个问题。这些故障给公司造成了时间和金钱损失,也给客户带来了问题,因为他们有一段时间无法使用他们的汽车。测试软件确实需要花钱,但公司也可以节省数百万的技术支持费用。
单元测试侧重于检查软件是否正确运行,这意味着检查输入和输出之间的映射是否都正确完成。作为低级别测试活动,单元测试有助于尽早识别错误,从而不会将其传播到软件系统的更高级别。
单元测试的其他优点包括:
设计测试策略
现在让我们看看如何设计测试策略。
在开始规划测试策略之前,有一个重要的问题需要回答。你想测试软件系统的哪些部分?
这是一个关键问题,因为穷举测试是不可能的。因此,你无法测试所有可能的输入和输出,但你应该根据所涉及的风险对测试进行优先级排序。
在定义测试范围时,需要考虑许多因素:
一旦你定义了测试范围(指定你应该测试什么以及不应该测试什么),你就可以讨论一个好的单元测试应该具备的特性了。
在深入研究 Python 中的单元测试之前,还有一个步骤缺失。我们如何组织测试以使它们干净易读?我们使用一种称为准备、执行和断言 (AAA) 的模式。
准备、执行和断言 (AAA) 模式是用于编写和组织单元测试的常用策略。它的工作方式如下:
此策略通过分离测试的所有主要部分(设置、执行和验证)提供了一种干净的方法来组织单元测试。此外,单元测试更容易阅读,因为它们都遵循相同的结构。
Python 中的单元测试:unittest 或 pytest?
我们现在将讨论 Python 中的两个不同的单元测试框架。这两个框架是 unittest 和 pytest。
Python 标准库包含 unittest 单元测试框架。此框架的灵感来自 JUnit,它是 Java 中的单元测试框架。
正如官方文档中所述,unittest 支持我们将在这篇文章中提到的几个重要概念:
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 框架根据我们的需求提供不同的断言方法:
现在我们已经基本了解了如何使用 unittest 框架编写单元测试,让我们来看看另一个名为 pytest 的 Python 框架。
pytest 框架是一个 Python 单元测试框架,它具有一些相关的特性:
由于 pytest 默认情况下未安装,因此我们必须先安装它。请注意,pytest 需要 Python 3.7 。
安装 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 编写第一个测试。
我们将使用之前编写的 BankAccount 类,我们将测试与之前相同的 method。通过这种方式,更容易比较使用这两个框架编写测试所需的努力。
要使用 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,然后开始测试——因为额外的努力是值得的!
如果你喜欢这篇文章,你可能还会发现以下内容有用:
关于 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中文网其他相关文章!