下班前几分钟,我彻底弄懂了unittest单元测试框架

news2024/10/5 3:30:28

目录

  • 1. 什么是单元测试?
  • 2. `unittest` 框架基础概念
    • 2.1 `unittest` 的核心类和方法
    • 2.2 `unittest` 的基本使用
    • 2.3 常用的断言方法
  • 3. 单元测试的组织与运行
    • 3.1 组织测试用例
    • 3.2 运行测试
  • 4. 高级用法与常见模式
    • 4.1 `setUp` 和 `tearDown` 方法
    • 4.2 `setUpClass` 和 `tearDownClass` 方法
    • 4.3 跳过测试和期望失败
  • Ref

1. 什么是单元测试?

在现代软件开发中,测试是确保代码质量的核心步骤之一。随着项目规模的扩大和功能的频繁更新,测试可以帮助开发者在迭代和维护阶段确保代码按照预期运行,并防止新功能引入不必要的错误。尤其是自动化测试,它通过快速、重复的验证,保障代码的稳定性和可维护性,降低了手动测试的成本和风险。

单元测试(Unit Testing)作为最基础的测试类型,专注于验证程序的最小逻辑单元——通常是函数或类的方法——是否在各种输入条件下正确执行。每个单元在测试时应是独立的,不依赖于系统的其他部分。通过对单独单元的测试,开发者可以迅速发现并修复代码中的问题,防止未来的修改引发回归错误(regression)。这样做不仅提升了代码的健壮性,还让未来的重构和功能扩展变得更加安全。

在 Python 生态系统中,unittest 是一个标准的单元测试框架,提供了一整套工具帮助开发者组织、编写并执行测试。它的核心优势在于其灵活性与可扩展性:unittest 允许开发者创建可重复的测试用例,使用丰富的断言方法来验证程序的行为是否符合预期。框架还提供了 setUp()tearDown() 方法,方便开发者在每个测试用例执行前后进行初始化和清理操作,使得测试过程更加自动化和系统化。

接下来,我们将深入探讨 unittest 框架,了解其基本用法、如何编写测试用例,以及一些常见的测试技巧和模式,帮助读者在实际项目中高效地应用这一工具。

2. unittest 框架基础概念

unittest 是 Python 中功能强大的标准化单元测试框架,它提供了丰富的工具用于编写、组织和运行测试。在项目开发中,测试的组织和结构化编写至关重要,而 unittest 框架正是通过一系列内置的类和方法帮助开发者系统化地完成这一任务。理解其核心类和方法,是我们高效使用该框架的第一步。

2.1 unittest 的核心类和方法

unittest 的核心是 unittest.TestCase 类,它为测试用例的编写提供了标准化的结构。每个测试用例都需要继承 TestCase 类,并通过实现具体的测试方法,验证代码功能的正确性。通过这一类,开发者不仅能够编写独立的测试用例,还能通过断言(assertion)方法来验证实际输出是否符合预期。

以下是 unittest 中几个关键的核心类和方法:

  • TestCase:所有测试用例的基类。每个具体的测试类都应继承此类并实现至少一个测试方法。TestCase 类提供了多种断言方法,以确保代码行为的正确性。
  • assert 系列方法TestCase 提供了一系列以 assert 开头的方法,用于验证某些条件是否为真。例如,assertEqual(a, b) 用于判断两个值是否相等,assertTrue(x) 用于验证某个表达式是否为真,这些断言是单元测试中最常用的工具。
  • setUp()tearDown() 方法:这些方法允许我们在每个测试用例执行前后进行额外的操作。setUp() 方法在每个测试执行前自动运行,适用于资源初始化或环境配置;tearDown() 方法则在每个测试执行后自动运行,用于释放资源或清理测试环境。这种钩子机制能够确保每个测试在一致的环境中独立运行,从而避免测试之间的相互干扰。

通过 TestCase 类和这些辅助方法,我们可以编写结构化的测试用例,确保代码在各个环节的表现都符合预期。

2.2 unittest 的基本使用

为了更好地理解 unittest 的使用方法,我们可以通过具体的代码示例来展示其实际应用。假设我们有一个简单的 add(a, b) 函数,该函数返回两个数的和。接下来我们将编写针对这一函数的单元测试用例,并展示如何使用 unittest 验证其行为。

import unittest

def add(a, b):
    return a + b

class TestAddFunction(unittest.TestCase):
    def test_add_integers(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(-1, 1), 0)

    def test_add_floats(self):
        self.assertAlmostEqual(add(1.1, 2.2), 3.3, places=1)

    def test_add_strings(self):
        self.assertEqual(add('hello', ' world'), 'hello world')

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

在这个示例中,我们定义了一个名为 TestAddFunction 的测试类,并通过继承 unittest.TestCase 来构建测试用例。每个以 test_ 开头的方法都代表一个测试用例,框架会自动识别并执行这些方法。在 test_add_integerstest_add_floatstest_add_strings 中,分别验证了 add 函数处理整数、浮点数和字符串的行为是否符合预期。我们使用了 assertEqualassertAlmostEqual 等断言方法,确保结果符合预期值。

当运行该文件时,unittest 会自动执行所有以 test_ 开头的测试方法,并在命令行输出测试结果。如果所有断言都成功,则测试通过;如果某个断言失败,unittest 会详细报告失败原因,帮助开发者快速定位问题。

2.3 常用的断言方法

断言(assertion)是单元测试的核心,通过断言方法我们可以验证代码输出是否符合预期。unittest 提供了多种断言方法,允许开发者以简洁的方式检查代码的正确性。以下列出了一些常用的断言方法及其用途:

  • assertEqual(a, b):断言 ab 相等。常用于验证函数返回值是否符合预期。
  • assertNotEqual(a, b):断言 ab 不相等。用于确保特定操作不会返回相同的结果。
  • assertTrue(x):断言表达式 x 为真。用于检查某个条件是否成立。
  • assertFalse(x):断言表达式 x 为假。用于验证某个条件不成立的场景。
  • assertIsNone(x):断言 xNone。用于确保变量在预期中没有被赋值。
  • assertIsNotNone(x):断言 x 不为 None。常用于验证某些资源被正确初始化。
  • assertIn(a, b):断言 ab 的子元素。用于验证一个元素是否存在于集合、列表或字典中。
  • assertNotIn(a, b):断言 a 不是 b 的子元素。用于确保某个元素不在某个集合内。

使用这些断言方法,开发者可以轻松地覆盖代码中的多种情况。它们不仅有助于发现功能上的错误,还能防止潜在的边界问题或不符合预期的行为,提升代码的可靠性和健壮性。

3. 单元测试的组织与运行

单元测试不仅仅是编写测试用例,它还涉及测试的组织和执行。尤其在大型项目中,测试代码的合理组织能够提高可维护性、清晰度,并帮助自动化测试流程顺利进行。unittest 框架提供了一套灵活的机制,帮助开发者高效地管理和运行单元测试。无论是小型模块的简单测试,还是复杂系统中的全面测试,合理的测试结构都能确保代码在各个阶段都维持高质量标准。

3.1 组织测试用例

在实际开发中,良好的测试用例组织结构至关重要。通常,开发者会将测试代码与应用代码分开存放,以确保代码库的清晰性,同时便于测试的管理和自动化集成。例如,典型的目录结构可能会将应用代码存放在 src/ 目录下,而将测试代码放在 tests/ 目录下。这种组织方式不仅直观,还能方便地在持续集成(CI/CD)系统中进行测试执行。

一个常见的项目目录结构如下:

project/
│
├── src/
│   └── app.py
│
└── tests/
    └── test_app.py

test_app.py 文件中,开发者可以为 app.py 中的函数或类编写对应的单元测试。这样的结构清晰明了,便于在项目的开发、调试和测试过程中快速定位代码和测试文件。更重要的是,这种分离的设计能够确保测试的独立性,使得开发者可以专注于应用功能和测试逻辑的各自实现,而不互相干扰。

为了有效管理大量的测试文件,unittest 框架提供了 TestLoader 类,它可以自动发现和加载测试文件中的所有测试用例,并通过 TestSuite 将这些测试用例组织起来,进行统一的运行和管理。通过这种方式,开发者可以将不同模块的测试组合在一起,便于批量执行和调试。

以下是使用 TestSuite 来统一管理测试的示例代码:

import unittest
from tests import test_app

def suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(test_app.TestAppFunction))
    return suite

if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    runner.run(suite())

在这个例子中,suite() 函数通过 unittest.TestSuite() 创建了一个测试套件,并使用 unittest.makeSuite() 加载 test_app.py 中的所有测试用例。TestLoaderTestSuite 的结合使得测试文件可以系统化管理,特别是在大型项目中,这种方式有助于提高测试的组织性和执行效率。

3.2 运行测试

unittest 框架提供了多种运行测试的方式,方便开发者根据需求灵活选择。在简单的场景下,最直接的方式是通过命令行运行测试文件。利用 -m unittest 命令,可以自动发现并执行指定测试文件中的所有测试用例:

python -m unittest tests/test_app.py

运行该命令后,unittest 会自动扫描 tests/test_app.py 中的所有以 test_ 开头的方法,并执行它们。测试结果会以可读的格式输出:若所有测试通过,结果会显示为 OK;若某个测试失败,测试框架将详细报告失败的原因,包括错误信息、断言失败的位置以及堆栈跟踪。这种清晰的输出能够帮助开发者迅速定位问题,从而加速调试过程。

此外,unittest 还支持通过 unittest.main() 函数直接在测试文件中运行测试。通常,我们可以在测试文件的末尾添加如下代码,使其在被直接运行时自动执行所有测试用例:

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

通过这种方式,当开发者在命令行直接运行该文件时,unittest 会自动发现并执行其中的所有测试用例,无需额外的配置。这一特性为单文件测试和调试提供了极大的便利。

总之,unittest 的运行方式非常灵活,不论是通过命令行运行单个测试文件,还是通过 TestSuite 组合多文件测试,它都能够为不同规模的项目提供强大的测试支持。此外,在持续集成系统中,这些运行方式也能够轻松地与 CI/CD 工具结合,实现自动化测试流程。

4. 高级用法与常见模式

unittest 框架中,除了基本的测试用例和断言之外,框架还提供了丰富的高级功能,能够满足复杂测试场景的需求。这些高级功能包括测试的生命周期管理、条件控制以及针对特定情景下的测试跳过或标记等。这些特性不仅让测试更加灵活,还能帮助开发者在不同环境下保持高效的测试流程,确保代码质量。

4.1 setUptearDown 方法

在实际测试中,我们通常会遇到需要在每个测试用例执行之前进行特定初始化操作的场景。例如,创建临时数据、建立数据库连接等操作是测试的前提条件。同样地,在测试执行完毕后,清理这些临时资源或关闭连接也是必不可少的。为了解决这些问题,unittest 提供了 setUp()tearDown() 方法,它们分别在每个测试用例执行前后自动调用,用于执行自定义的初始化和清理逻辑。

以下是 setUp()tearDown() 方法的典型使用场景:

class TestDatabase(unittest.TestCase):
    def setUp(self):
        # 初始化数据库连接
        self.db = DatabaseConnection()
        self.db.connect()

    def tearDown(self):
        # 关闭数据库连接
        self.db.disconnect()

    def test_insert(self):
        # 测试插入操作
        self.db.insert('data')
        self.assertEqual(self.db.count(), 1)

    def test_delete(self):
        # 测试删除操作
        self.db.insert('data')
        self.db.delete('data')
        self.assertEqual(self.db.count(), 0)

在上面的例子中,setUp() 方法在每个测试用例执行之前调用,用于初始化数据库连接,而 tearDown() 方法则在每个测试用例结束后调用,负责关闭数据库连接。通过这种方式,我们可以确保每个测试用例在一个独立、干净的环境中执行。这种设计不仅增强了测试的稳定性,也简化了测试资源的管理,避免了不同测试用例之间的相互影响。

4.2 setUpClasstearDownClass 方法

虽然 setUp()tearDown() 非常适合在每个测试用例前后执行初始化和清理操作,但在某些情况下,我们可能只需要在整个测试类执行之前进行一次性操作。例如,初始化一个数据库连接或设置测试环境,这种操作在所有测试用例中都是共享的,而不需要每次重复进行。为了应对这种场景,unittest 提供了 setUpClass()tearDownClass() 两个类方法,它们只会在测试类的开始和结束时分别执行一次。

class TestDatabase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # 在所有测试之前运行一次
        cls.db = DatabaseConnection()
        cls.db.connect()

    @classmethod
    def tearDownClass(cls):
        # 在所有测试之后运行一次
        cls.db.disconnect()

    def test_insert(self):
        # 测试插入操作
        self.db.insert('data')
        self.assertEqual(self.db.count(), 1)

    def test_delete(self):
        # 测试删除操作
        self.db.insert('data')
        self.db.delete('data')
        self.assertEqual(self.db.count(), 0)

在这个例子中,setUpClass() 方法在整个测试类开始之前执行,用于建立一次性数据库连接;而 tearDownClass() 方法则在所有测试用例执行完毕后调用,用于关闭连接。这种方法特别适合那些初始化和清理操作较为耗时的场景,能够显著提高测试执行效率,同时减少资源的重复消耗。

4.3 跳过测试和期望失败

在某些特定情况下,开发者可能希望跳过部分测试用例,或者标记一些测试为“预期失败”。这些情况可能包括功能尚未实现、环境不支持某些功能,或者某些功能的行为在特定条件下尚未确定。unittest 提供了多种装饰器,帮助我们灵活控制测试用例的执行,特别是在开发过程中或多平台测试时,这些功能显得尤为重要。

以下是 unittest 中常见的跳过和期望失败装饰器:

  • @unittest.skip(reason):无条件跳过测试。reason 参数用于说明跳过测试的原因,帮助团队成员了解跳过的动机。
  • @unittest.skipIf(condition, reason):如果条件成立,则跳过测试。常用于基于环境或配置的测试控制,例如跳过在特定操作系统上无法运行的测试。
  • @unittest.expectedFailure:标记测试为“预期失败”。这种标记用于表示开发者已经意识到该测试目前会失败,但不希望它影响整体测试结果。这种情况通常用于正在开发中的功能,避免在功能未完全实现时报告错误。

以下是跳过测试和期望失败的示例代码:

class TestExample(unittest.TestCase):
    @unittest.skip("演示跳过测试")
    def test_skip(self):
        self.assertEqual(1, 1)

    @unittest.skipIf(not hasattr(dict, 'items'), "跳过,因为字典没有 items 方法")
    def test_skip_if(self):
        self.assertEqual(1, 1)

    @unittest.expectedFailure
    def test_expected_failure(self):
        self.assertEqual(1, 0)

在这个例子中,test_skip() 方法被无条件跳过,而 test_skip_if() 则基于条件决定是否跳过。当运行这些测试时,如果某个测试被跳过,unittest 会在输出中标记出跳过的原因。对于 expectedFailure 装饰器,虽然测试会失败,但不会影响整体测试状态,这为逐步开发提供了灵活性。


Ref

[1] https://docs.python.org/3/library/unittest.html
[2] https://realpython.com/python-testing/
[3] https://www.datacamp.com/community/tutorials/unit-testing-python

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2189165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

老年人意外跌倒感知技术

意外跌倒是导致老年人仙游的6大原因之一,尤其多余80岁以上的老年人。跌倒已成为我国 65 岁以上老年人因伤致死的首位原因(来源:IT之家)。 跌倒最容易发生在两个地方,卫生间和过道。主要可能是卫生间没有安装扶手&…

关于Generator,async 和 await的介绍

在本篇文章中我们主要围绕下面几个问题来介绍async 和await 🍰Generator的作用,async 及 await 的特点,它们的优点和缺点分别是什么?await 原理是什么? 📅我的感受是我们先来了解Generator,在去…

将视频改成代码滚动

本文章就来讲讲如何将视频转换成代码滚动,也就是这种模式: 本文章就来详细的教大家如何制作达到这种效果吧! (注:我记得一些python库也可以轻松达到这些效果,但我一时半伙想不起来了,所以这里用…

idea插件开发的第六天-开发一个笔记插件

介绍 Demo说明 本文基于maven项目开发,idea版本为2022.3以上,jdk为1.8本文在JTools插件之上进行开发本插件目标是做一款笔记插件,用于开发者在开发过程中随时记录信息仓库地址: jtools-notes JTools插件说明 Tools插件是一个Idea插件,此插件提供统一Spi规范,极大的降低了id…

手写mybatis之Mapper XML的解析和注册使用

前言 你是怎么面对功能迭代的? 很多程序员在刚开始做编程或者新加入一家公司时,都没有多少机会可以做一个新项目,大部分时候都是在老项目上不断的迭代更新。在这个过程你可能要学习N个前人留下的各式各样的风格迥异的代码片段,在这…

【杂谈一之概率论】CDF、PDF、PMF和PPF概念解释与分析

一、概念解释 1、CDF:累积分布函数(cumulative distribution function),又叫做分布函数,是概率密度函数的积分,能完整描述一个实随机变量X的概率分布 2、PDF:连续型概率密度函数(p…

平面电磁波的电场能量磁场能量密度相等,能量密度的体积分等于能量,注意电场能量公式也没有复数形式(和坡印廷类似)

1、电场能量密度和磁场能量密度相等(实数场算的) 下面是电场能量密度和磁场能量密度的公式,注意这可不是坡印廷定理。且电场能量密度没有复数表达式,即不是把E和D换成复数形式就行的。注意,一个矢量可以转化为复数形式,两个矢量做…

数据挖掘-padans初步使用

目录标题 Jupyter Notebook安装启动 Pandas快速入门查看数据验证数据建立索引数据选取⚠️注意:排序分组聚合数据转换增加列绘图line 或 **(默认):绘制折线图。bar:绘制条形图。barh:绘制水平条形图。hist&…

Discord:报错:A fatal Javascript error occured(解决办法)

按 Windows 键 R 并输入 %appdata% 选择 discord 文件夹并将其删除。 再次按 Windows 键 R 并输入 %LocalAppData% 选择 discord 文件夹并再次将其删除。 附加: 如果还不行,就通过官网下载吧,这个问题通过epic下载可能会有

图文深入理解Oracle DB企业级集中管理神器-GC的安装和部署

值此国庆佳节,深宅家中,闲来无事,就多写几篇博文。今天继续宅继续写。 本文承接上篇,介绍GC的安装和部署。咱们不急,慢慢来,饭要一口一口地吃才能吃得踏实自然。 限于篇幅,本节将重点介绍关键步…

【ubuntu】apt是什么

目录 1.apt简介 2.常用apt指令 2.1安装 2.2更新列表 2.3更新已经安装的软件包 2.4搜索软件包 2.5显示软件包信息 2.6移除软件包 2.7清理无用的安装包 2.8清理无用的依赖项 3.apt和apt-get 3.1区别 3.2 总结 1.apt简介 apt的全称是advanced package …

JAVA的三大特性-封装、继承、多态

Java作为一种面向对象的编程语言,其核心特性包括封装、继承和多态。这三大特性是Java语言的基石,它们相互关联,共同构成了Java强大的面向对象能力。 封装(Encapsulation) 封装是面向对象编程的一个重要概念&#xff0c…

Pytorch最最适合研究生的入门教程,Q3 开始训练

文章目录 Pytorch最最适合研究生的入门教程Q3 开始训练3.1 训练的见解3.2 Pytorch基本训练框架work Pytorch最最适合研究生的入门教程 Q3 开始训练 3.1 训练的见解 如何理解深度学习能够完成任务? 考虑如下回归问题 由函数 y f ( x ) yf(x) yf(x)采样得到的100个…

现在的新电脑在任务管理器里又多了个NPU?它是啥?

前言 今年中旬各家品牌的新笔记本感觉上都是很不错,搞得小白自己心痒痒,突然间想要真的买一台Windows笔记本来耍耍了。 但今天这个文章并不是什么商品宣传啥的,而是小白稍微尝试了一下新笔记本之后的一些发现。 在今年的新笔记本上都多了一…

【GESP】C++一级练习BCQM3025,输入-计算-输出-6

题型与BCQM3024一样,计算逻辑上稍微复杂了一点点,代码逻辑没变,仍属于小学3,4年级的题目水平。 题解详见:https://www.coderli.com/gesp-1-bcqm3025/ https://www.coderli.com/gesp-1-bcqm3025/https://www.coderli.c…

数据提取之JSON与JsonPATH

第一章 json 一、json简介 json简单说就是javascript中的对象和数组,所以这两种结构就是对象和数组两种结构,通过这两种结构可以表示各种复杂的结构 > 1. 对象:对象在js中表示为{ }括起来的内容,数据结构为 { key&#xff1…

最新版本SkyWalking【10.1.0】部署

这里写目录标题 前言前置条件启动Skywalking下载解压启动说明 集成Skywalking Agent下载Agent在IDEA中添加agent启动应用并访问SpringBoot接口 说明 前言 基于当前最新版10.1.0搭建skywalking 前置条件 装有JDK11版本的环境了解SpringBoot相关知识 启动Skywalking 下载 地…

浑元换算策略和武德换算策略-《分析模式》漫谈36

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第3章有这么一句: A conversion, however deterministic, does not follow that faithfully. 2004(机械工业出版社)中译本…

HTB:Explosion[WriteUP]

目录 连接至HTB服务器并启动靶机 1.What does the 3-letter acronym RDP stand for? 2.What is a 3-letter acronym that refers to interaction with the host through a command line interface? 3.What about graphical user interface interactions? 4.What is the…

【MySQL 08】复合查询

目录 1.准备工作 2.多表查询 笛卡尔积 多表查询案例 3. 自连接 4.子查询 1.单行子查询 2.多行子查询 3.多列子查询 4.在from子句中使用子查询 5.合并查询 1.union 2.union all 1.准备工作 如下三个表,将作为示例,理解复合查询 EMP员工表…