使用 Python unittest 模块进行单元测试

为何单元测试至关重要?

优秀的开发者绝不会在未经过全面测试的情况下部署代码。单元测试是针对大型程序中各个独立模块进行的测试过程,它能确保代码的质量和可靠性。

本文将深入探讨如何运用 Python 的 unittest 模块来实施单元测试。首先,让我们简要了解测试的不同类型。

测试方法主要分为手动测试和自动化测试。手动测试是指由人工在开发完成后执行的测试过程,而自动化测试则是通过程序自动执行测试并给出结果。

显而易见,手动测试耗时且难以规模化。因此,开发者通常会编写自动化测试代码。自动化测试包含多种类型,包括单元测试、集成测试、端到端测试和压力测试等。

让我们审视一下标准的测试流程:

  • 编写或更新代码。
  • 针对不同场景,为代码编写或更新测试用例。
  • 运行测试(手动或使用测试运行器)。
  • 检查测试结果。如有错误,修正并重复以上步骤。

本文将侧重于单元测试,它是最基本、也是最重要的测试类型。接下来,我们将深入研究实际操作。

什么是单元测试?

单元测试是一种针对一小块独立代码进行测试的技术。通常,这小块代码是一个函数。所谓的“独立”,是指该代码不依赖于项目中的其他代码。

例如,我们需要验证一个字符串是否等于“techblik.com”。为此,我们编写了一个函数,该函数接受一个字符串参数,并返回它是否与“techblik.com”相等。

  def is_equal_to_geekflare(string):
	return string == "techblik.com"

上述函数不依赖于任何其他代码,因此我们可以通过提供不同的输入来独立地测试它。这种独立的代码块可以在整个项目中使用。

单元测试的重要性

通常,独立的模块代码可在整个项目中复用,因此必须经过精心编写和测试。单元测试正是用于测试这些独立代码块的方法。如果项目不进行单元测试,会发生什么呢?

假设我们未对项目中使用的小代码块进行测试,那么所有其他依赖于这些代码块的测试(例如集成测试、端到端测试)可能会失败,导致应用程序崩溃。这凸显了对代码基本组成部分进行充分测试的重要性。

我们现在清楚了对所有独立代码块进行单元测试并编写单元测试用例的重要性。通过进行单元测试,我们可以确保其他测试不会因基本代码模块的问题而失败。

在接下来的章节中,我们将学习 Python 的 unittest 模块及其在编写单元测试中的应用。

注意:我们假定您已熟悉 Python 的类、模块等概念。如果您对 Python 中级概念(例如类和模块)不太了解,可能会难以理解接下来的部分。

Python 单元测试简介

Python unittest 是一个用于测试 Python 代码的内置测试框架。它提供了一个测试运行器,使我们能够轻松执行测试。因此,我们可以使用内置的 unittest 模块进行测试,而无需依赖第三方库,但这也会根据需求变化。内置的 unittest 模块非常适合在 Python 中开始测试。

我们必须遵循以下步骤来使用 unittest 模块测试 Python 代码:

  1. 编写代码。
  2. 导入 unittest 模块。
  3. 创建一个以 “test” 关键字开头的文件。例如,test_prime.py。”test” 关键字用于标识测试文件。
  4. 创建一个继承自 unittest.TestCase 的类。
  5. 在该类中编写测试方法。每种方法都包含一个不同的测试用例。方法名称必须以 “test” 关键字开头。
  6. 运行测试。我们可以使用多种方式运行测试。
    • 运行命令 python -m unittest test_filename.py
    • 我们可以像运行一般的 Python 文件一样,使用命令 python test_filename.py 来运行测试文件。为了让此方法生效,我们需要在测试文件中调用 unittest 的 main 方法。
    • 最后,使用发现机制。我们可以使用命令 python -m unittest discover 来自动运行测试,而无需显式指定测试文件名。它会按照我们遵循的命名约定来查找测试。因此,我们必须在测试文件名开头使用 “test” 关键字。

通常,在测试中,我们会将代码的输出与预期的输出进行比较。为此,unittest 提供了多种比较方法。您可以在这里找到比较方法的列表。

这些方法都非常简单易懂。

说了这么多理论,现在让我们开始编码吧!

注意:如果您对 unittest 模块有任何疑问,请参考官方文档,解决您的疑问。接下来,让我们开始使用 unittest 模块。

在 Python 中使用 unittest 进行单元测试

首先,我们将编写一些函数,然后专注于编写测试。在您喜欢的代码编辑器中打开一个文件夹,并创建一个名为 utils.py 的文件。将以下代码粘贴到该文件中:

import math


def is_prime(n):
    if n < 0:
        return 'Negative numbers are not allowed'

    if n <= 1:
        return False

    if n == 2:
        return True

    if n % 2 == 0:
        return False

    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True


def cubic(a):
    return a * a * a


def say_hello(name):
    return "Hello, " + name

我们在 utils.py 文件中有三个不同的函数。现在,我们必须使用不同的测试用例来测试每个函数。让我们为第一个函数 is_prime 编写测试。

  1. 在示例文件夹中,创建一个名为 test_utils.py 的文件,与 utils.py 文件同级。
  2. 导入 utilsunittest 模块。
  3. 创建一个名为 TestUtils 的类,继承 unittest.TestCase 类。类名可以任意取,但要尽量使其具有描述性。
  4. 在该类中,编写一个名为 test_is_prime 的方法,并将其 self 作为参数。
  5. is_prime 编写带有不同参数的测试用例,并将输出与预期输出进行比较。
  6. 示例测试用例:self.assertFalse(utils.is_prime(1))
  7. 在上述情况下,我们期望 is_prime(1) 的输出为 False
  8. 类似于上述案例,我们将根据正在测试的函数来编写不同的测试案例。

让我们看看具体的测试代码:

import unittest

import utils


class TestUtils(unittest.TestCase):
    def test_is_prime(self):
        self.assertFalse(utils.is_prime(4))
        self.assertTrue(utils.is_prime(2))
        self.assertTrue(utils.is_prime(3))
        self.assertFalse(utils.is_prime(8))
        self.assertFalse(utils.is_prime(10))
        self.assertTrue(utils.is_prime(7))
        self.assertEqual(utils.is_prime(-3),
                         "Negative numbers are not allowed")


if __name__ == '__main__':
    unittest.main()

我们正在调用 unittest 模块的 main 方法,以便使用命令 python filename.py 运行测试。现在,让我们运行测试。

您将看到类似以下内容的输出:

$ python test_utils.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

现在,请尝试为其他函数编写测试用例。考虑函数的各种情况,并为其编写测试。以下是在上述类中添加的测试:

...


class TestUtils(unittest.TestCase):
    def test_is_prime(self):
        ...

    def test_cubic(self):
        self.assertEqual(utils.cubic(2), 8)
        self.assertEqual(utils.cubic(-2), -8)
        self.assertNotEqual(utils.cubic(2), 4)
        self.assertNotEqual(utils.cubic(-3), 27)

    def test_say_hello(self):
        self.assertEqual(utils.say_hello("techblik.com"), "Hello, techblik.com")
        self.assertEqual(utils.say_hello("Chandan"), "Hello, Chandan")
        self.assertNotEqual(utils.say_hello("Chandan"), "Hi, Chandan")
        self.assertNotEqual(utils.say_hello("Hafeez"), "Hi, Hafeez")


...

我们只使用了 unittest 模块中的一些比较函数。您可以在这里找到完整的列表。

我们已经学习了如何使用 unittest 模块编写单元测试。现在,让我们看看运行测试的不同方法。

如何使用 unittest 运行测试

上一节中,我们已经了解了一种运行测试的方法。接下来,让我们看看使用 unittest 模块运行测试的另外两种方法。

  1. 使用文件名和 unittest 模块。

在这种方法中,我们将使用 unittest 模块和文件名来运行测试。运行测试的命令是 python -m unittest filename.py。在我们的示例中,运行测试的命令是 python -m unittest test_utils.py

  1. 使用发现机制。

我们将使用 unittest 模块的 discover 方法来自动检测所有测试文件并运行它们。为了能够自动检测到测试文件,我们需要以 “test” 关键字开头来命名它们。

使用 discover 方法运行测试的命令是 python -m unittest discover。该命令将检测所有名称以 “test” 开头的文件并执行它们。

结论

单元测试是编程领域中基础的测试类型。当然,现实世界中还存在许多其他类型的测试,建议大家逐步学习。希望本教程能够帮助您掌握使用 unittest 模块在 Python 中编写基本的测试用例。除了 unittest 之外,还有诸如 pytest、Robot Framework、nose、nose2 和 slash 等第三方库,您可以根据项目需求进行探索。

祝您测试愉快!😎

您可能还会对 Python 面试问题与解答感兴趣。