Python 课程10-单元测试
前言
在现代软件开发中,单元测试 已成为一种必不可少的实践。通过测试,我们可以确保每个功能模块在开发和修改过程中按预期工作,从而减少软件缺陷,提高代码质量。而测试驱动开发(TDD) 则进一步将测试作为开发的核心部分,先编写测试,再编写代码,以测试为指导开发出更稳定、更可靠的代码。
Python 提供了强大的 unittest
模块,它是 Python 标准库的一部分,专门用于编写和执行单元测试。与其他测试框架相比,unittest
具有以下优势:
- 完全符合 Python 的标准,无需安装额外的包。
- 提供了多种内置的断言方法,能够覆盖常见的测试场景。
- 支持测试套件和测试运行器的管理,方便组织和执行大量的测试。
本篇详细教程将带你深入了解如何使用 unittest 编写测试用例,并通过 测试驱动开发(TDD) 的方式引导你编写健壮的代码。我们将通过大量的实例,逐步讲解单元测试的各个方面,帮助你系统掌握如何通过测试提高代码质量。
目录
-
单元测试概述
- 单元测试的定义与作用
- 为什么要编写单元测试?
-
unittest 模块详解
unittest
模块简介- 如何编写基础测试用例
- 常见断言方法详解
assertEqual()
assertTrue()
和assertFalse()
assertIn()
和assertNotIn()
assertRaises()
- 组织测试:测试套件与测试运行器
- 使用
setUp()
和tearDown()
进行测试准备与清理 - 示例:为一个简单的数学函数编写测试
-
深入理解测试驱动开发(TDD)
- TDD 的核心理念
- TDD 的工作流程
- TDD 的优点与挑战
- 示例:通过 TDD 开发一个简单的应用
-
单元测试的进阶用法
- 使用
mock
模拟外部依赖 - 使用参数化测试减少重复代码
- 如何测试异常与错误处理
- 如何为类编写测试
- 如何编写性能测试和长时间运行的测试
- 使用
1. 单元测试概述
单元测试的定义与作用
单元测试 是对软件中最小的可测试单位(通常是单个函数或方法)进行验证的一种测试方法。单元测试的目标是确保这个最小单位在开发、重构或扩展过程中,始终按预期工作。
在软件开发的不同阶段,单元测试起到了以下几个重要作用:
- 确保代码功能正确:单元测试帮助验证每个功能模块是否能按预期执行,确保逻辑正确性。
- 及早发现错误:通过单元测试,开发者能够在开发早期阶段发现问题,减少后期修复成本。
- 支持代码重构:在重构或优化代码时,单元测试可以验证改动是否破坏了现有功能。
- 提升代码可维护性:通过为代码编写测试,可以让未来的维护人员更快地理解和修改代码。
为什么要编写单元测试?
-
减少Bug:在没有单元测试的情况下,代码中的 Bug 可能会被遗漏,直到系统运行时才被发现。而通过单元测试,开发者可以在编写代码时,立即发现问题。
-
增加信心:当你对代码进行修改或重构时,单元测试可以帮助验证改动是否影响了其他功能,让你对系统的整体稳定性更有信心。
-
促进良好的代码设计:单元测试鼓励开发者编写模块化、职责单一的代码,因为这样的代码更容易测试。
-
文档化功能:编写的单元测试也是对代码功能的详细描述,能够帮助其他开发者理解代码的用途和预期行为。
2. unittest 模块详解
unittest
模块简介
unittest 是 Python 内置的测试框架,类似于其他语言中的 JUnit
和 NUnit
。它是一个轻量级的测试框架,能够用于编写、管理和运行单元测试。使用 unittest
可以编写测试用例,设置测试环境,并检查代码在各种情况下的表现。
如何编写基础测试用例
在 unittest
中,每个测试用例是 unittest.TestCase
的子类。编写一个测试用例的基本步骤如下:
- 创建一个继承自
unittest.TestCase
的测试类。 - 在测试类中定义测试方法,方法名称必须以
test_
开头。 - 在测试方法中,使用
unittest
提供的断言方法来检查结果。 - 使用
unittest.main()
来运行测试。
示例代码如下:
import unittest# 被测试的函数
def add(a, b):return a + b# 编写测试用例
class TestMathFunctions(unittest.TestCase):def test_add(self):self.assertEqual(add(1, 2), 3)self.assertEqual(add(-1, 1), 0)self.assertEqual(add(0, 0), 0)# 运行测试
if __name__ == '__main__':unittest.main()
在上述代码中,我们为 add
函数编写了一个测试类 TestMathFunctions
。测试类中的 test_add
方法验证了函数在不同输入下的输出是否符合预期。
常见断言方法详解
断言方法用于检查某些条件是否成立,若条件不成立,测试将失败。以下是 unittest 提供的常用断言方法:
-
assertEqual(a, b)
:检查a
是否等于b
。self.assertEqual(add(1, 2), 3) # 成功
-
assertTrue(x)
和assertFalse(x)
:检查x
是否为True
或False
。self.assertTrue(5 > 3) # 成功 self.assertFalse(3 > 5) # 成功
-
assertIn(a, b)
和assertNotIn(a, b)
:检查a
是否在b
中,或者不在b
中。self.assertIn(3, [1, 2, 3]) # 成功 self.assertNotIn(4, [1, 2, 3]) # 成功
-
assertRaises(Exception, callable, *args, **kwargs)
:检查是否抛出指定的异常。with self.assertRaises(ZeroDivisionError):result = 1 / 0
组织测试:测试套件与测试运行器
-
测试套件:将多个测试用例组合到一起。
def suite():suite = unittest.TestSuite()suite.addTest(TestMathFunctions('test_add'))return suiteif __name__ == '__main__':runner = unittest.TextTestRunner()runner.run(suite())
-
测试运行器:负责运行测试套件,并输出测试结果。
通过
unittest.TextTestRunner()
可以创建一个测试运行器,它负责管理测试执行,并报告测试结果。
使用 setUp()
和 tearDown()
进行测试准备与清理
在编写测试时,有时需要为每个测试方法设置测试环境,或者在测试结束时进行清理工作。unittest
提供了两个方法 setUp()
和 tearDown()
,分别在每个测试用例执行前后自动调用。
setUp()
:在每个测试方法执行前调用,用于初始化资源。tearDown()
:在每个测试方法执行后调用,用于释放资源。
示例代码:
import unittestclass TestExample(unittest.TestCase):def setUp(self):print("Setting up the test environment...")def tearDown(self):print("Cleaning up the test environment...")def test_example(self):print("Running the test...")self.assertEqual(1 + 1, 2)if __name__ == '__main__':unittest.main()
示例:为一个简单的数学函数编写测试
我们现在为一个乘法函数编写单元测试:
# 被测试的函数
def multiply(a, b):return a * b# 编写测试用例
class TestMathFunctions(unittest.TestCase):def test_multiply(self):# 测试常规情况self.assertEqual(multiply(2, 3), 6)self.assertEqual(multiply(-1, 5), -5)self.assertEqual(multiply(0, 100), 0)# 测试边界条件self.assertEqual(multiply(1, 1), 1)self.assertEqual(multiply(999999, 0), 0)# 运行测试
if __name__ == '__main__':unittest.main()
在这个例子中,测试类 TestMathFunctions
对 multiply()
函数进行了常规和边界条件的测试,以确保函数在不同情况下的正确性。
3. 深入理解测试驱动开发(TDD)
什么是测试驱动开发?
测试驱动开发(Test-Driven Development, TDD) 是一种软件开发方法,它要求开发者在编写功能代码之前先编写测试用例。TDD 的核心理念是通过测试来驱动开发过程,确保代码实现的功能完全符合需求。
TDD 的主要步骤如下:
- 编写一个失败的测试:在功能实现之前,先编写测试用例。由于功能尚未实现,测试应当失败。
- 编写代码使测试通过:编写足够的代码来通过刚才的测试,代码应满足测试用例中的需求。
- 重构代码:在测试通过的前提下,重构代码以提高其可读性和维护性。
- 重复上述步骤:不断迭代,逐步完善功能。
TDD 的工作流程
TDD 的开发过程一般分为以下三步(又称 红-绿-重构 循环):
- 红色阶段:编写一个尚未实现的功能的测试,运行测试并确认测试失败(红色表示失败)。
- 绿色阶段:编写最少量的代码使测试通过,测试结果变为绿色。
- 重构阶段:重构刚刚编写的代码,确保代码简洁、可读,同时确保所有测试仍然通过。
TDD 的优点与挑战
TDD 的优点:
- 提高代码质量:TDD 通过提前编写测试用例,确保功能在开发时就得到了充分的测试。
- 减少 Bug:由于每个功能的实现都需要通过测试验证,代码中的 Bug 被及早发现和修复。
- 简化重构:重构代码时,已有的测试用例可以帮助验证代码的正确性,避免引入新 Bug。
- 清晰的需求文档:测试用例实际上也是需求的一种形式,能够清晰地表达功能的预期行为。
TDD 的挑战:
- 初期成本高:TDD 需要先编写测试,可能会增加开发的初期时间成本。
- 对开发者的要求高:开发者需要清晰地了解功能需求,并能够将其转化为测试用例。
- 可能导致过度设计:有时开发者可能会过度关注如何让测试通过,而忽略了功能的实际实现。
示例:通过 TDD 开发一个简单的应用
我们现在通过一个简单的示例,展示如何使用 TDD 的方法开发一个计算平方根的函数。
第一步:编写一个失败的测试
在实现功能之前,我们先编写一个测试用例,测试 sqrt()
函数是否能正确计算平方根。
import unittest# 编写测试用例
class TestMathFunctions(unittest.TestCase):def test_sqrt(self):self.assertEqual(sqrt(4), 2)self.assertEqual(sqrt(16), 4)# 测试负数应该抛出异常self.assertRaises(ValueError, sqrt, -1)if __name__ == '__main__':unittest.main()
此时,sqrt()
函数还没有实现,因此运行测试会失败。
第二步:编写代码使测试通过
现在我们来实现 sqrt()
函数,使其通过测试用例。
import mathdef sqrt(x):if x < 0:raise ValueError("Cannot calculate the square root of a negative number")return math.sqrt(x)
通过这一小段代码,我们满足了测试用例的需求,即:
- 对于非负数,返回其平方根。
- 对于负数,抛出
ValueError
异常。
第三步:重构代码
当前的代码已经非常简洁,无需进一步重构。我们可以继续添加更多的功能,重复进行 TDD 流程。
4. 单元测试的进阶用法
在实际项目中,单元测试并不仅限于对简单函数进行测试。我们可能还需要处理外部依赖、测试复杂的类以及编写性能测试。本节将介绍一些单元测试中的高级技巧。
使用 mock
模拟外部依赖
在单元测试中,有时我们需要模拟外部服务(如数据库、网络请求等)的行为。unittest.mock
提供了模拟外部依赖的能力,帮助我们隔离测试目标代码。
from unittest import TestCase
from unittest.mock import patch# 假设我们有一个函数需要调用外部 API 获取数据
def get_weather_data(api_url):# 调用外部 APIresponse = requests.get(api_url)return response.json()class TestWeatherData(TestCase):@patch('requests.get')def test_get_weather_data(self, mock_get):# 模拟返回的 JSON 数据mock_get.return_value.json.return_value = {'weather': 'sunny'}result = get_weather_data('http://fakeapi.com/weather')self.assertEqual(result['weather'], 'sunny')if __name__ == '__main__':unittest.main()
在此例中,我们使用 @patch
模拟了 requests.get
方法,避免在测试时真正调用外部 API。
使用参数化测试减少重复代码
对于某些具有多个输入输出对的测试用例,可以使用参数化测试来减少重复代码。
from parameterized import parameterized
import unittestdef add(a, b):return a + bclass TestMathFunctions(unittest.TestCase):@parameterized.expand([(1, 2, 3),(-1, 1, 0),(0, 0, 0),])def test_add(self, a, b, expected):self.assertEqual(add(a, b), expected)if __name__ == '__main__':unittest.main()
通过 parameterized.expand()
,我们可以一次性测试多个输入组合,避免为每个测试单独编写代码。
如何测试异常与错误处理
在测试中,常常需要检查程序是否在遇到非法输入时抛出了正确的异常。使用 assertRaises()
方法可以测试函数是否按预期抛出异常。
class TestMathFunctions(unittest.TestCase):def test_divide_by_zero(self):with self.assertRaises(ZeroDivisionError):result = 1 / 0
如何为类编写测试
当测试类的方法时,每个方法需要分别测试,以确保类的所有行为都符合预期。
class Calculator:def add(self, a, b):return a + bdef subtract(self, a, b):return a - bclass TestCalculator(unittest.TestCase):def setUp(self):self.calculator = Calculator()def test_add(self):self.assertEqual(self.calculator.add(1, 2), 3)def test_subtract(self):self.assertEqual(self.calculator.subtract(5, 3), 2)if __name__ == '__main__':unittest.main()
如何编写性能测试和长时间运行的测试
对于某些可能需要长时间运行的测试,可以使用 time
模块记录代码的运行时间,检查其性能。
import time
import unittestclass TestPerformance(unittest.TestCase):def test_long_running_task(self):start_time = time.time()# 模拟一个长时间运行的任务time.sleep(2)end_time = time.time()execution_time = end_time - start_timeself.assertTrue(execution_time >= 2)if __name__ == '__main__':unittest.main()
结论
通过本篇详细的教程,你已经深入掌握了如何使用 unittest 模块编写单元测试,以及如何运用 测试驱动开发(TDD) 来提高代码的可靠性。在实际项目中,单元测试不仅能帮助你发现问题,减少 Bug,还能为代码的重构和维护提供坚实的保障。
相关文章:

Python 课程10-单元测试
前言 在现代软件开发中,单元测试 已成为一种必不可少的实践。通过测试,我们可以确保每个功能模块在开发和修改过程中按预期工作,从而减少软件缺陷,提高代码质量。而测试驱动开发(TDD) 则进一步将测试作为开…...

【嵌入式硬件开发基础】Arduino板常用外设及应用:MPU6050空间运动传感器(简介,类库函数,卡尔曼滤波),继电器(原理介绍,含应用实例/代码)
当一个人不能拥有的时候,他唯一能做的便是不要忘记。 🎯作者主页: 追光者♂🔥 🌸个人简介: 📝[1] CSDN 博客专家📝 🏆[2] 人工智能领域优质创作者🏆 🌟[3] 2022年度博客之星人工智能领域TOP4🌟 🌿[4] 2023年城市之星领跑者TOP1(哈尔滨…...

Pandas Series对象创建,属性,索引及运算详解
目录 Series对象创建 实例化参数 index参数 选用array-like创建Series对象 list ndarray 显示索引与隐式索引 选用dict创建Series对象 不指定索引 指定索引 选用标量创建Series对象 使用标量创建的广播机制 Series属性 name size shape index values Series索…...
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
遗传算法(Genetic Algorithm, GA)是一种启发式搜索算法,用于寻找复杂优化问题的近似解。它模拟了自然选择和遗传学中的进化过程,主要用于解决那些传统算法难以处理的问题。 遗传算法的基本步骤: 初始化种群࿰…...

高等数学 2.3 高阶导数
一般地,函数 y f ( x ) y f(x) yf(x) 的导数 y ′ f ′ ( x ) y\ f\ (x) y ′f ′(x) 仍然是 x x x 的函数。我们把 y ′ f ′ ( x ) y\ f\ (x) y ′f ′(x) 的导数叫做函数 y f ( x ) y f(x) yf(x) 的二阶导数,记作 y ′ ′ y\ y ′…...

app抓包 chrome://inspect/#devices
一、前言: 1.首先不支持flutter框架,可支持ionic、taro 2.初次需要翻墙 3.app为debug包,非release 二、具体步骤 1.谷歌浏览器地址:chrome://inspect/#devices qq浏览器地址:qqbrowser://inspect/#devi…...

SAP自动化-ME12批量更新某行价格
Python源码 #-Begin-----------------------------------------------------------------#-Includes-------------------------------------------------------------- import sys, win32com.client import os#-Sub Main----------------------------------------------------…...

数据库系统 第58节 概述源码示例
深入探讨数据库技术,我们将通过具体的源代码示例来进一步解释数据库分区、复制、集群和镜像等高级特性。 数据库分区的源代码示例 哈希分区 在PostgreSQL中,可以使用哈希分区来创建一个分区表: CREATE TABLE measurements (city_id …...

软件设计师——程序设计语言
目录 低级语言和高级语言 编译程序和解释程序 正规式,词法分析的一个工具 有限自动机 编辑 上下文无关法 编辑 中后缀表示法 杂题 编辑 低级语言和高级语言 编译程序和解释程序 计算机只能理解由0、1序列构成的机器语言,因此高级程序设计…...

【在Linux世界中追寻伟大的One Piece】五种IO模型和阻塞IO
目录 1 -> 五种IO模型 1.1 -> 阻塞IO(Blocking IO) 1.2 -> 非阻塞IO(Non-blocking IO) 1.3 -> 信号驱动IO(Signal-Driven IO) 1.4 -> IO多路转接(IO Multiplexing) 1.5 -> 异步IO(Asynchronous IO) 2 -> 高级IO概念 2.1 -> 同步通信VS异步通信…...

nginx实现权重机制(nginx基础配置二)
在上一篇文章中我们已经完成了对轮询机制的测试,详情请看轮询机制。 接下来我们进行权重机制的测试 一、conf配置 upstream backServer{ server 127.0.0.1:8080 weight2; server 127.0.0.1:8081 weight1; } server { listen 80; server_name upstream.boyatop.cn…...

华为的仓颉和ArkTS这两门语言有什么区别
先贴下官网: ArkTs官网 仓颉官网 ArkTS的官网介绍说,ArkTS是TypeScript的进一步强化版本,简单来说就是包含了TS的风格,但是做了一些改进。 了解TypeScript的朋友都应该知道,其实TypeScript就是JavaScript的改进版本&…...

(SERIES10)DM逻辑备份还原
1 概念 逻辑备份还原是对数据库逻辑组件(如表、视图和存储过程等数据库对象)的备份还原。逻辑导出(dexp)和逻辑导入(dimp)是 DM 数据库的两个命令行工具,分别用来实现对 DM 数据库的逻辑备份和逻…...

Java零基础-StringBuilder类详解
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互…...

免费爬虫软件“HyperlinkCollector超链采集器v0.1”
HyperlinkCollector超链采集器单机版v0.1 软件采用python的pyside2和selenium开发,暂时只支持window环境,抓取方式支持普通程序抓取和selenium模拟浏览器抓取。软件遵守robots协议。 首先下载后解压缩,然后运行app目录下的HyperlinkCollector.exe 运行…...

OPENAIGC开发者大赛企业组AI黑马奖 | AIGC数智传媒解决方案
在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给您…...

k8s(kubernetes)的PV / PVC / StorageClass(理论+实践)
NFS总是不支持PVC扩容 先来个一句话总结:PV、PVC是K8S用来做存储管理的资源对象,它们让存储资源的使用变得可控,从而保障系统的稳定性、可靠性。StorageClass则是为了减少人工的工作量而去自动化创建PV的组件。所有Pod使用存储只有一个原则&…...

前端Excel热成像数据展示及插值算法
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏:《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️生活的理想,就是为了理想的生活! 目录 📘 前言 📘一、热成像数…...

VBA_NZ系列工具NZ01: VBA二维码应用技术
我的教程一共九套及VBA汉英手册一部,分为初级、中级、高级三大部分。是对VBA的系统讲解,从简单的入门,到数据库,到字典,到高级的网抓及类的应用。大家在学习的过程中可能会存在困惑,这么多知识点该如何组织…...

小明震惊OpenAI 的新模型 01
在硅谷的中心,繁忙的咖啡馆和创业中心周围,年轻的软件工程师小明坐在他的办公桌前,面露困惑。科技界一直在盛传一项新的AI突破,但他持怀疑态度,不敢抱太大希望。他认为AI泡沫即将破灭,炒作列车即将出轨&…...

Clickhouse使用笔记
clickhouse官方文档:https://clickhouse.com/docs/zh/sql-reference/data-types/decimal 一,建表 create table acitivity_user_record ( id String DEFAULT generateUUIDv4(), -- 主键自增 activityId String, userId String, userName Nullable(Strin…...

基于高通主板的ARM架构服务器
一、ARM架构服务器的崛起 (一)市场需求推动 消费市场寒冬,全球消费电子需求下行,服务器成半导体核心动力之一。Arm 加速布局服务器领域,如 9 月推出 Neoverse V2。长久以来,x86 架构主导服务器市场&#…...

AV1 Bitstream Decoding Process Specification--[2]:符号和缩写术语
原文地址:https://aomediacodec.github.io/av1-spec/av1-spec.pdf没有梯子的下载地址:AV1 Bitstream & Decoding Process Specification摘要:这份文档定义了开放媒体联盟(Alliance for Open Media)AV1视频编解码器…...

【Python爬虫系列】_022.异步文件操作aiofiles
课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈...

GD32E230 RTC报警中断功能使用
GD32E230 RTC报警中断使用 GD32E230 RTC时钟源有3个,一个是内部RC振动器产生的40KHz作为时钟源,或者是有外部32768Hz晶振.,或者外部高速时钟晶振分频作为时钟源。 🔖个人认为最难理解难点的就是有关RTC时钟异步预分频和同步预分频的计算。在对…...

C/C++语言基础--从C到C++的不同(上)
本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 之前更新的C语言,感谢大家的点赞收藏关注,接下来我们逐步也开始更新C;C语言后面也会继续更新知识点,如内联汇编;本人现在正在写一个C语言的图书管理系…...

自动驾驶自动泊车场景应用总结
自动泊车技术是当前智能驾驶技术的一个重要分支,其目标是通过车辆自身的感知、决策和控制系统,实现车辆在有限空间内的自主泊车操作。目前自动泊车可分为半自动泊车、全自动泊车、记忆泊车、自主代客泊车四种产品形态,其中, 根据搭载传感器和使用场景的不同,全自动泊车又可…...

redis常见的数据类型?
参考:一文读懂Redis五种数据类型及应用场景 - 知乎 (zhihu.com) String 类型 String 类型:Redis 最基本的数据类型,它是二进制安全的,意味着你可以用它来存储任何类型的数据,如图片、序列化对象等。使用场景ÿ…...

TCP Analysis Flags 之 TCP ZeroWindow
前言 默认情况下,Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态,并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时,会对每个 TCP 数据包进行一次分析,数据包按照它们在数据包列表中出现的顺序进行处理。可…...

[产品管理-16]:NPDP新产品开发 - 14 - 产品创新流程 - 产品创新流程模型比较:门径、IPD、精益生产、敏捷、系统工程、设计思维、精益创业
目录 一、精益开发与敏捷开发的比较 1、核心理念 2、实践方式 3、应用场景 4、总结 二、门径流程 VS 敏捷方法 1、定义与特点 门径管理流程 敏捷方法 2、应用场景 3、比较 4、总结 三、集成产品开发 VS 系统工程 VS 设计思维 1、集成产品开发(IPD&…...