Python 中的单元测试:快速概述

在 SDLC 的不同阶段进行测试非常关键。 测试可以是单元测试、集成测试、负载测试、压力测试等。测试对于保证系统在不同场景和输入条件下的稳定性很重要。

这里 这就是太空任务所发生的事情,通过足够的单元测试和 linting(测量代码质量和遵守标准的过程)可以避免(希望)。 让人起鸡皮疙瘩,不是吗?

单元测试 毫无疑问,这是每个开发人员都应该具备的最重要的技能之一。 任何具有数千或数百万个移动部件的大型应用程序都无法手动测试回归。

单元测试 是一种完全自动化的测试最小代码段的方法,也称为单元。 这里的主要目标是在本地机器上而不是在生产环境中破坏代码。

单元测试在以下几个方面与传统的手动测试不同:

  1. 它是完全自动化的。 编写的测试用例经过编码并自动或手动运行(如本文所示)。 在工作中,人们可能会注意到构建或部署管道在合并和/或部署代码之前运行测试。
  2. 测试最小的代码单元。 最小的单元通常意味着一个方法或一个函数,但它也可以是一行代码。
  3. 它允许“测试驱动开发”,一种敏捷开发策略,在完成任何数量的代码之前编写测试,并在开发完成后检查测试是否通过。

单元测试的好处

提高代码质量

编写测试迫使我们创建易于测试的功能组件。 这种做法使代码模块化和可维护。

早期检测软件错误

没有代码是没有错误的,优秀的开发人员应该知道哪种情况会导致错误。 单元测试可以检测在手动测试软件时可能被认为很少见的场景,但是,在 Live 中,此类场景可能会更频繁地发生。

Edge 案例场景预先测试

在将代码部署到实时环境之前,可以很好地识别整数溢出、IndexOutOfBound 甚至 NullPointers 等情况。 我们还应该考虑到代码应该在某些场景下抛出错误,单元测试是测试这些错误是否真的被抛出的好方法。

降低成本和开发时间

编写大量的单元测试确实需要相当长的时间,但是,考虑到它们提供的好处,从长远来看,它们是有回报的。 在开发阶段早期没有发现的错误会被跳到测试和生产中,以便在调试、错误修复和部署上花费更多时间。

简化文档

单元测试本身就是开发人员代码的文档。 正在为其编写单元测试的每个类或文件都涵盖了所有不同的场景,阅读单元测试应该能够充分了解组件试图实现的目标。

强化部署和代码提交管道

部署管道可以设置为在系统部署之前甚至在部署之前运行单元测试,同时我们将代码提交给版本控制系统(Git,for example)。 这确保了部署不会失败,并且添加的任何新功能都不会破坏已经工作的功能。

单元测试 包裹

Python 带有 unittest 包,最初是受 JUnit 启发的。 但是,我们不需要安装任何软件包。 有一些重要的概念 单元测试 像在 Junit 中一样支持。

测试夹具

如果一个人来自 Java 和 Junit 背景,他们必须熟悉单元测试中使用的 setUp() 和 tearDown()。 在 python 中,测试装置做同样的事情。 它们可用于创建临时目录、数据库连接、伪造品、模拟等 设置() 并销毁这些对象和临时目录和连接 拆除().

测试用例

测试用例类似于我们为测试功能而创建的类。 它检查针对输入组合返回的响应。

测试套件

在为代码运行测试时,可能希望将多个测试组合在一个保护伞下,以便它们一起运行。 我们可以对测试用例和/或测试套件进行分组。

测试赛跑者

测试运行器具有编排测试执行的魔力。 在本文后面,您可能会注意到,呈现的结果可以是文本格式和/或图形界面。

理论够了!! 让我们做一些测试……

下表列出了一些最常用的断言方法,由 测试用例 类来检查和报告失败。

方法检查那个
assertEqual(a, b)一种 == b
assertNotEqual(a, b)一种 != b
断言真(x)布尔(X) 是真的
断言假(x)布尔(X) 是假的
断言是(a,b)一种 b
assertIsNot(a, b)一种 不是 b
断言无(x)X 是无
assertIsNotNone(x)X 不是无
断言输入(a,b)一种 b
assertNotIn(a, b)一种 不是 在 b
断言IsInstance(a, b)实例(一,乙)
assertNotIsInstance(a, b)不是实例(一,乙)

一个基本的例子

让我们尝试实现一些测试 计算器.py

class Calculator:

    def add(self, a: int, b: int) -> int:
        return a + b

    def sub(self, a: int, b: int) -> int:
        return a - b

    def mul(self, a: int, b: int) -> int:
        return a * b

    def div(self, a: int, b: int) -> float:
        return a / b

Calculator_test.py 测试我们的计算器逻辑。 请注意,我们也考虑负面情况。

import unittest

from Calculator import Calculator

class CalculatorTest(unittest.TestCase):

    def setUp(self) -> None:
        super().setUp()
        self.calculator = Calculator()

    def test_add(self):
        # Check add(5, 6) gives 11 or not
        self.assertEqual(11, self.calculator.add(5, 6),
                         "Testing Calculator.add()")

    def test_sub(self):
        # Check sub(5, 6) gives -1 or not
        self.assertEqual(-1, self.calculator.sub(5, 6),
                         "Testing Calculator.sub()")

    def test_sub_fail(self):
        # This test case will fail as sub(5, 6) is -1 not 10
        self.assertEqual(10, self.calculator.sub(5, 6), "Testing Calculator.sub()")

    def test_mul(self):
        # Run single test multiple times with different parameters
        # Parameter Source

params = [

{

'a': 5,

'b': 6,

'ret': 30

},

{

'a': 5,

'b': -10,

'ret': -50

},

{

'a': 0,

'b': 6,

'ret': 0

},

{

'a': 9,

'b': -6,

'ret': -54

},

]

对于参数中的参数:

self.assertEqual(param['ret'], self.calculator.mul(

param['a'], param['b']), "Testing Calculator.mul()")

def test_div(self):

# Check div(40, 2) gives 20 or not

self.assertEqual(20, self.calculator.div(

40, 2), "Testing Calculator.div()")

# Check div(20, 40) gives 0.5 or not

self.assertEqual(20/40, self.calculator.div(20, 40),

"Testing Calculator.div()")

# Check div(10, 3) gives 3.3333333 or not (rounded upto 7 decimal places)

self.assertAlmostEqual(3.3333333, self.calculator.div(10, 3),

msg="Testing Calculator.div()")

# Check whether Divide by 0 throws exception ZeroDivisionError

self.assertRaises(ZeroDivisionError, self.calculator.div, 20, 0)

if __name__ == "__main__":

unittest.main()

一个测试用例继承 单元测试.TestCase. 设置() 准备测试夹具并将在执行每个测试方法之前运行。 各个测试方法的名称需要以 测试_ 得到测试运行者的认可。
子测试() 上下文管理器用于区分测试方法体内的测试迭代。

我们测试的输出如下所示:

所有测试用例都按预期运行。

但是,如果您使用 IDE 之类的 皮查姆 或者 VSCode 然后是一个 GUI 测试运行器,它以图形方式显示成功和失败。

VSCode 打开测试文件,按 Shift + P 并选择 Python:运行当前测试文件

您应该看到失败和通过的测试用例

输出更具描述性:

最佳实践

在编写单元测试时,我们应该遵循的最佳实践很少。

安排、行动和断言

安排: 我们编写的每个测试,首先我们为测试安排我们的数据。

行为: 在这个阶段,我们对打算测试的方法或组件采取行动

断言: 在这里,我们断言我们从组件接收到的输出。

确保在我们测试的每个阶段之后都有一个行空间,并且每个阶段的代码都可以组合在一起。

之前和之后

确保应添加每个测试所需的通用代码 测试夹具. 使用 setUp() 在每次测试之前运行逻辑,使用 tearDown() 用于在测试之后运行逻辑。

假货与假货

始终尽量避免模拟并使用实际组件本身。 但是,如果无法避免,请尝试使用 假货,我们可以在其中伪造组件、服务或某些 I/O 操作。 嘲笑 应该是测试时采取的最后手段,因为模拟会做出很多假设,这些假设在实际场景中可能成立,也可能不成立。

当测试方法涉及 RPC (REST/GraphQL/gRPC) 或数据库查询时,我们会模拟有线调用。 但这超出了本文的范围,将在以后的文章中介绍。

有意义的名字

确保您的测试具有有意义的名称,并且读者可以通过阅读测试名称来猜测发生了什么。

结论

今天到此为止。 请记住,只有在您编写代码时才会引入错误。 所以要么测试你添加的每一行代码,要么根本不写代码! 快乐编码! ?