单元测试:为工程质量保驾护航
单元测试
单元测试是软件开发过程中确保代码质量和正确性的关键手段。它指的是对软件中的最小可测试单元(通常是函数或方法)进行验证,确保其行为符合预期。
基本概念
单元测试
:验证软件中最小单元(通常是函数或方法)的正确性,确保其独立性和可重复性。测试用例
:描述一组输入和预期输出,用于验证软件单元的行为是否正确。测试覆盖率
:衡量单元测试覆盖程序代码量的指标,分为行覆盖率、分支覆盖率和路径覆盖率等。
重要性
早期发现 BUG
:在开发过程中早期发现和修复缺陷,降低修复成本。代码重构保障
:在进行代码重构时确保现有功能不被破坏。文档和示例
:单元测试可以作为代码的实际使用示例,提供良好的文档支持。持续集成
:单元测试是持续集成和持续交付的基础,确保每次代码变更不会引入新的问题。
常用技术
断言
((\textit{Assertions})):用于验证代码执行结果是否符合预期。测试框架
:- Python:
unittest
,pytest
- Java:
JUnit
- JavaScript:
Jest
,Mocha
- Python:
Mock 测试
:用于模拟和隔离外部依赖(如数据库、网络服务)。- Python:
unittest.mock
- Java:
Mockito
- JavaScript:
Sinon.js
- Python:
功能测试
功能代码
我们使用 Python 实现以下 UserService
、EmailService
和 UserServiceWithEmail
三个类,来演示单元测试的基本概念。
# user_service.py
class UserService:def __init__(self):self.users = {} # 使用字典来存储用户信息def add_user(self, user_id, name):# 添加用户if user_id in self.users:raise ValueError("User ID already exists")self.users[user_id] = namedef get_user(self, user_id):# 查找用户return self.users.get(user_id, None)def delete_user(self, user_id):# 删除用户if user_id in self.users:del self.users[user_id]else:raise ValueError("User ID does not exist")# email_service.py
class EmailService:def send_email(self, email_address, subject, content):# 模拟发送电子邮件print(f"Sending email to {email_address} with subject {subject}")return True# email_decorator.py
class UserServiceWithEmail(UserService):def __init__(self, email_service):super().__init__()self.email_service = email_servicedef add_user(self, user_id, name, email_address):super().add_user(user_id, name)self.email_service.send_email(email_address, "Welcome!", "Thank you for registering!")
单测代码
单元测试的目标是确保每个单独的函数或方法在与系统其他部分隔离的情况下工作正常,所以需要码验证了各种可能的操作场景。
接下我们分别对 UserService
和 UserServiceWithEmail
进行测试。
# test_user_service.py
import unittest
from user_service.py import UserService
from email_service.py import EmailService
from email_decorator.py import UserServiceWithEmail
from unittest.mock import MagicMockclass TestUserService(unittest.TestCase):def setUp(self):# 说明:每个测试方法运行前都会执行 `setUp` 方法,初始化一个 `UserService` 实例,确保每个测试在相同的初始状态下进行。self.service = UserService()def test_add_user(self):# 说明:测试添加用户功能。添加一个用户后,使用 `get_user` 方法检索该用户,并验证返回结果是否与添加的一致。self.service.add_user(1, "John")self.assertEqual(self.service.get_user(1), "John")def test_add_user_existing_id(self):# 说明:测试添加重复用户 ID 的情况。第一次添加用户成功后,尝试用相同的 ID 添加新用户,验证是否抛出 `ValueError` 异常。self.service.add_user(1, "John")with self.assertRaises(ValueError):self.service.add_user(1, "Jane")def test_get_user_non_existing(self):# 说明:测试获取不存在的用户。调用 `get_user` 方法查询一个不存在的用户 ID,验证返回值是否为 `None`。self.assertIsNone(self.service.get_user(999))def test_delete_user(self):# 说明:测试删除用户功能。首先添加一个用户,然后删除该用户,验证该用户是否已经被成功删除(查询时返回 `None`)。self.service.add_user(1, "John")self.service.delete_user(1)self.assertIsNone(self.service.get_user(1))def test_delete_user_non_existing(self):# 说明:测试删除不存在的用户。尝试删除一个不存在的用户,验证是否抛出 `ValueError` 异常。with self.assertRaises(ValueError):self.service.delete_user(999)class TestUserServiceWithEmail(unittest.TestCase):def setUp(self):self.email_service = EmailService()# 说明:使用 `MagicMock` 对象 Mock(模拟) `send_email` 方法,使其在测试过程中返回 `True` 而不是实际发送电子邮件。初始化测试对象时,将 Mock 对象传入测试函数。self.email_service.send_email = MagicMock(return_value=True)self.service = UserServiceWithEmail(self.email_service)def test_add_user_sends_email(self):# 说明:测试添加用户并发送欢迎电子邮件的功能。添加用户后,验证 `send_email` 方法是否被调用一次,且参数正确。user_id = 1name = "John"email_address = "john@example.com"self.service.add_user(user_id, name, email_address)self.email_service.send_email.assert_called_once_with(email_address, "Welcome!", "Thank you for registering!")def test_add_user_existing_id(self):# 说明:测试在电子邮件版本的用户服务中添加重复用户 ID 的情况。预计会抛出 `ValueError`。self.service.add_user(1, "John", "john@example.com")with self.assertRaises(ValueError):self.service.add_user(1, "Jane", "jane@example.com")def test_delete_user(self):# 说明:测试删除用户功能。同样地,先添加用户,再删除,最后验证该用户是否已经被成功删除。self.service.add_user(1, "John", "john@example.com")self.service.delete_user(1)self.assertIsNone(self.service.get_user(1))if __name__ == "__main__":unittest.main()
Mock 测试
基本概念
Mock 测试
是一种在软件测试中模拟对象或行为的技术。在测试某个单元(通常是一个函数或类)时,通过创建“虚拟对象”来模拟系统中的真实对象或依赖,替代它所依赖的其他组件,以便隔离待测试单元并专注于其自身的逻辑。通过使用 mock 对象,可以控制这些外部依赖的行为和状态,从而确保测试的确定性和一致性。
主要作用
隔离测试
:确保测试只关注目标组件,而不受其他组件或外部系统的影响。控制行为
:可以设定模拟对象的返回值或行为,以测试不同场景。提高效率
:避免与数据库、网络等真实服务的交互,提高测试速度。
常见场景
Mock 测试常用于单元测试,帮助开发者确保代码在预期条件下的表现。
- 替代数据库调用,以避免对实际数据库的写操作。
- 模拟网络请求,测试响应处理逻辑。
- 模拟复杂的对象或系统行为,以简化测试环境。
常用的 mock 框架有 Python 的 unittest.mock 和 JavaScript 的 Jest Mocks。
Mock 对象
上文的 TestUserServiceWithEmail
中,MagicMock
对象模拟了 EmailService
的 send_email
,演示了 Mock 方法
。此处我们再举一个例子,演示一下 Mock 对象
。
假设我们有一段依赖外部服务的代码:
# user_service.py
class UserService:def __init__(self, api_client):self.api_client = api_clientdef get_user_data(self, user_id):# 依赖 api_client 所调用的外部服务response = self.api_client.get(f"/users/{user_id}")if response.status_code == 200:return response.json()else:raise ValueError("User not found")
我们可以使用 Mock 对象来模拟 api_client
的行为,以便测试 UserService
的 get_user_data
方法:
# test_user_service.py
import unittest
from user_service import UserService
from unittest.mock import MagicMockclass TestUserService(unittest.TestCase):def setUp(self):# 使用 MagicMock 对象模拟 api_clientself.api_client = MagicMock()self.user_service = UserService(self.api_client)def test_get_user_data_success(self):# 让 user_service.get_user_data 中,模拟的外部服务调用(api_client.get)直接返回成功(200)的结果self.api_client.get.return_value.status_code = 200self.api_client.get.return_value.json.return_value = {"id": 1, "name": "John"}user_data = self.user_service.get_user_data(1)self.assertEqual(user_data, {"id": 1, "name": "John"})def test_get_user_data_failure(self):# 让 user_service.get_user_data 中,模拟的外部服务调用(api_client.get)直接返回失败(404)的结果self.api_client.get.return_value.status_code = 404with self.assertRaises(ValueError):self.user_service.get_user_data(1)if __name__ == '__main__':unittest.main()
使用 @patch
使用 @patch
装饰器可以进一步简化和清晰化测试逻辑。它允许我们在测试之前设置 Mock 对象,并在测试结束后自动恢复原始对象,减少手动处理的复杂性。
以下我们使用 @patch
装饰器重构上文中 TestUserServiceWithEmail
的测试代码:
# test_user_service.py
import unittest
from email_service import EmailService
from email_decorator import UserServiceWithEmail
from unittest.mock import patchclass TestUserServiceWithEmail(unittest.TestCase):...# 说明:使用 `@patch` 装饰器 Mock `send_email` 方法,使其在测试过程中返回 `True` 而不是实际发送电子邮件。初始化测试对象时,将 Mock 对象传入测试函数。@patch('email_service.EmailService.send_email', return_value=True)def setUp(self, mock_send_email):self.mock_send_email = mock_send_emailself.service = UserServiceWithEmail(EmailService())...
@patch
优势
使用 @patch
装饰器重构后的代码与之前手动 Mock 的代码相比,具有以下优点和特点:
-
简化了 Mock 对象的创建和恢复:
- 之前:需要在
setUp
方法中手动创建 Mock 对象,并在测试方法中调用它。 - 现在:通过
@patch
装饰器,可以直接在测试方法中获得 Mock 对象,同时测试结束后自动恢复原始对象,减少了手动操作和错误可能性。
# 使用 MagicMock 手动 Mock self.email_service.send_email = MagicMock(return_value=True)# 使用 @patch 自动 Mock @patch('email_service.EmailService.send_email', return_value=True)
- 之前:需要在
-
增强代码清晰度:
- 之前:在进行 Mock 时,代码中需要额外维护 Mock 对象的状态。
- 现在:
@patch
装饰器使得测试代码更加简洁和直观,将 Mock 逻辑与实际测试逻辑解耦。
# 使用 MagicMock 手动 Mock self.email_service.send_email.assert_called_once_with(...)# 使用 @patch 自动 Mock self.mock_send_email.assert_called_once_with(...)
-
集中管理 Mock:
- 之前:需要在每个测试方法中手动处理 Mock 对象。
- 现在:通过
@patch
装饰器,可以在类级别或方法级别集中管理 Mock 对象,使得 Mock 配置更容易理解和维护。
此外使用 @patch
还有以下特点:
上下文管理
:@patch
的另一优势在于它能够管理 Mock 对象的生命周期,上下文管理器特性使得在大的测试类或测试文件中不会出现混乱的状态问题。测试隔离性
:使用@patch
时,各个测试方法之间的 Mock 状态是相互隔离的。这确保了一个测试方法中的 Mock 不会影响其他测试方法,增强了测试的可靠性。
使用 @patch
装饰器可以显著改善单元测试代码的简洁性和可维护性,使 Mock 对象的配置和恢复更为直观和自动化。它有助于提高测试代码的清晰度和隔离性,特别适用于复杂的测试场景和依赖多个外部服务的系统。掌握和应用这一技术,是提高代码质量和测试效率的重要工具。
Mock 测试的思考
Mock 测试使我们可以隔离单元测试,确保单元功能的正确性,不受外部依赖的影响,但也带来一些挑战和需要注意的事项:
过度 Mock
:过度依赖 Mock 可能导致测试体系与实际运行环境脱节。应当只 Mock 那些外部依赖,而不是系统内部逻辑。行为验证
vs状态验证
:Mock 更关注行为验证(验证某些调用是否发生),而非状态验证(验证某些状态值)。在实际测试中,二者需要平衡使用。保持一致性
:Mock 对象的行为应当尽可能与真实对象一致,以避免测试结果和实际情况差异过大。
最佳实践
Arrange-Act-Assert 模式
Arrange
:设置测试场景和准备所需的状态。Act
:调用待测试的方法或函数。Assert
:验证结果是否符合预期。
单测技巧
- 使用
setUp
和tearDown
:使用setUp
和tearDown
方法来准备和清理测试环境,减少重复代码。确保每个测试在一个确定的状态下开始。 - 使用
assertRaises
:在异常处理
中使用assertRaises
方法来断言代码会在特定情况下抛出预期的异常。例如,尝试添加已经存在的用户 ID,需要抛出ValueError
。 状态验证
vs行为验证
:状态验证
:通过检查方法调用后的状态确保系统行为正确。行为验证
:用mock
验证方法的调用行为,比如利用assert_called_once_with
检查方法被正确调用。
- 使用
unittest.mock
:模拟外部依赖的行为。隔离单元测试,确保它们独立于外部系统(如网络、数据库)。验证调用次数和参数,确保函数的行为符合预期。 - 使用
@patch
:简化 Mock 对象的创建和管理,提供更清晰、更易读的测试代码。
Tips
独立性
:每个单元测试应独立运行,确保不会互相影响。这有助于更容易发现问题来源,便于调试。小范围测试
:每个测试应专注于一项功能,保持测试的精细度。避免在同一方法中进行多个断言,保持测试的明确性。清晰的命名和编码
:测试方法应有意义的命名,指明测试内容和预期结果,以便描述他们的测试内容和预期行为。确保测试代码易于理解,结构清晰,注释明了。模拟外部依赖
:使用@patch
或MagicMock
来隔离测试,保持测试专注于单元逻辑,不让外部因素(如数据库、网络请求等)影响结果。保障单测的可重复性,提高稳定性和速度。全面覆盖
:尽量编写覆盖各种可能情况的测试案例,包括边界条件和异常情况。以确保代码在各种情况下表现正确。确保代码健壮性。持续集成
:将单元测试集成到持续集成(CI)工作流中,确保每次代码变更后都能自动测试,避免引入新的缺陷。及时更新
:保持测试代码与生产代码同步更新,以避免测试数据的陈旧和不一致。保持测试代码清晰,易于理解和维护。覆盖率工具
:使用代码覆盖率工具(如coverage.py
)来确保测试覆盖了代码的各个部分,但也要注意覆盖率不是唯一的质量指标。
通过遵循这些最佳实践,可以确保单元测试的质量,提高代码的可靠性和可维护性。
结语
单元测试是确保代码质量和可靠性的关键手段之一。通过对代码的最小单元进行独立测试,开发者可以更早地发现和修复缺陷,同时在进行代码重构和变更时保持稳妥。Mock 测试可以有效地隔离外部依赖,使测试更加独立和可重复,但需要谨慎使用,以避免过度 Mock 导致的测试与实际场景脱节。从实际开发经验中,逐步积累完善单元测试技术和最佳实践,能显著提升开发效率和代码质量。
- 上一篇:时间戳:以不变应万变
- 专栏:「计算通践」
相关文章:

单元测试:为工程质量保驾护航
单元测试 单元测试是软件开发过程中确保代码质量和正确性的关键手段。它指的是对软件中的最小可测试单元(通常是函数或方法)进行验证,确保其行为符合预期。 基本概念 单元测试:验证软件中最小单元(通常是函数或方法…...

江协科技STM32学习笔记
第01章 STM32简介及开发环境搭建 1.1 STM32简介 1.1.1 STM32F103C8T6 系列:主流系列STM32F1 内核:ARM Cortex-M3 主频:72MHz RAM:20K(SRAM) ROM:64K(Flash) 供电…...

RabbitMQ再回首--往事如梦
这文章你就读吧,越读越🥸,一读一个不吱声 可靠的🐰警官:rabbitMQ,功能全面,不丢数据,体量小,容易堆积 声明exchange channel . exchangeDeclare ( String exchange , …...

头狼择校小程序
综述介绍 头狼择校,是头狼择™高校的简称,我们专注高校、大学的择校。倡导先嗅就业再择校,是预约工具和对话平台。帮您嗅招办、嗅教授、嗅学姐,预约择校有关的老师、顾问,助力考大学和考研的“双考”学生及家长了解就…...

【Electron】npm安装Electron项目失败报错问题和解决办法
前言 闲来无事,便想着研究一下Electron,没想到安装直接就卡住了 问题 npm ERR! RequestError: Hostname/IP does not match certificates altnames: Host: npm.taobao.org. is not in the certs altnames: DNS:*.tbcdn.cn, DNS:*.taobao.com, DNS:*.al…...

人工智能提示(prompt)工程入门
文章目录 人工智能提示(prompt)工程入门一、目的二、使用1、角色2、提示3、上下文4、例子5、输入6、输出 三、使用示例 人工智能提示(prompt)工程入门 一、目的 对于当前的发达的人工智能,我们可以广泛使用࿰…...

【机器学习的基本思想】模型优化与评估
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科,通过算法和模型让计算机从数据中学习,进行模型训练和优化,做出预测、分类和决策支持。Python成为机器学习的首选语言,…...

公司电脑监控软件推荐(一口气了解8款!)一起领略电脑监控界的刀光剑影!
企业的内部管理的需求日益复杂,电脑监控软件作为提升工作效率、保障数据安全的重要工具,其重要性不言而喻。今天,我们将带您一口气了解8款顶尖的公司电脑监控软件,包括国内知名的“安企神”以及多款来自海外的优秀产品,…...

设备图纸资料管理系统:数字化转型下的高效协同与安全管理新篇章
在当今高度信息化的时代,设备图纸资料管理系统作为企业资产与知识管理的重要一环,正日益凸显其不可或缺的价值。这一系统集成了先进的数字化技术与管理理念,旨在实现设备图纸资料的高效存储、快速检索、安全共享及版本控制,为企业…...

ArcGIS基础:标注转注记及简单处理
注记是一个静态的标签图层,能够独立的保存为文件,并且具有计算功能; 标注是一个动态的标签图形,无法以文件的形式进行存储和计算; 2者各有优势和劣势,根据具体需求进行选择 需要注意的是注记要存储在GDB…...

jQuery实现图片轮播效果
实现图片轮播效果,打开页面,每隔3秒切换至下一张图片;光标移入数字时,播放相应图片。 思路: (1)获取需要轮播的图片和展示的div。 (2)使用animate设置left值ÿ…...

关于天地图新手使用
1分钟带你了解学习天地图 适用新手 天地图API (tianditu.gov.cn) 文档api 先去注册key 把脚本放到index.html文件里面 <!-- 天地图的官网申请的tk --> <script src="http://api.tianditu.gov.cn/api?v=4.0&tk=申请的key" type="text/javascr…...

STM32与Arduino和ESP32对比分析
在嵌入式系统领域,STM32、Arduino 和 ESP32 是三种广泛使用的微控制器平台。它们各自具有独特的优势,适用于不同类型的项目。本文将详细比较这些平台,帮助您了解它们之间的差异、优势以及可能的应用场景。 一、架构与处理能力 STM32…...

125. 验证回文串【 力扣(LeetCode) 】
一、题目描述 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s,如果它是 回文串 ,返回 true ;…...

3年经验,面试测试岗20k都拿不到了吗?
我的情况 大概介绍一下个人情况,女,本科,三年多测试工作经验,懂python,会写脚本,会selenium,会性能,然而到今天都没有收到一份offer!从年后就开始准备简历,年…...

【ML】强化学习(Reinforcement Learning)及其拆解
【ML】强化学习(Reinforcement Learning) 1. RL Outline 强化学习(Reinforcement Learning)概述1.1 RL的基本框架 2. RL 引入:从这个小游戏开始3. Policy Gradient 方法4. Actor-Critic 方法5. [奖励塑形(R…...

在宝塔面板下安装WordPress
宝塔面板是服务器管理好助手,尤其在Linux系统下,提高了管理的可视化,降低了Linux服务器的使用门槛。 WordPress是个非常好的博客系统,由于支持海量主题模板、各种类型的插件,因此已经成为建设各类网站的首选框架。 今…...

纷享销客CRM AI产品架构概览、产品特色
一、纷享销客CRM AI产品架构概览 纷享AI平台架构分为三个主要层次:AI基础设施层、AI平台层和AI应用层。每个层次都由一系列功能模块组成,旨在为客户提供强大的技术支持和灵活的解决方案。 1.Al基础设施层 AI基础设施层是整个AI平台的底层支撑ÿ…...

【文件IO】文件系统操作
文章目录 基本操作概述1. 文件属性2. 文件构造方法3. 文件方法1. 文件创建2. 文件删除3. 查看目录下所有的文件名4. 遍历目录5. 创建目录5. 目录重命名 基本操作概述 创建文件删除文件创建目录重命名文件判定文件存在… Java 中,提供了一个 File 类,进…...

Spring Cloud Alibaba 集成分布式定时任务调度功能
作者:千习 背景简介 定时任务是指在约定的时间,或者按照固定频率周期性执行的任务。在企业应用中,非用户行为发起的后台业务,一般都是通过定时任务来实现,常见场景如下: 异步数据处理:比如先…...

中职云计算实训室
一、实训室建设背景 随着信息技术的飞速发展,云计算已成为推动数字化转型、促进经济社会发展的重要力量。《中华人民共和国国民经济和社会发展第十四个五年规划和2035年远景目标纲要》明确提出,要加快数字化发展,建设数字中国。云计算作为数…...

【python制作一个小程序作为七夕礼物】
制作一个七夕节礼物的小程序,我们可以考虑一个简单的互动程序,比如一个“七夕情侣姓名配对指数计算器”。这个程序将接收两个名字作为输入,然后输出一个随机的“配对指数”和一些浪漫的话语。以下是一个使用Python实现的简单示例:…...

一篇文章教会你如何使用Haproxy,内含大量实战案例
1. Haproxy 介绍 HAProxy是法国开发者 威利塔罗(Willy Tarreau) 使用C语言编写的自由及开放源代码软件,是一款具备高并发(万级以上)、高性能的TCP和HTTP应用程序代理. HAProxy运行在当前的硬件上,可以支持…...

PythonStudio 控件使用常用方式(二十二)TFlowPanel
PythonStudio是一个极强的开发Python的IDE工具,官网地址是:https://glsite.com/ ,在官网可以下载最新版的PythonStudio,同时,在使用PythonStudio时,它也能及时为用户升到最新版本。它使用的是Delphi的控件&…...

培训第二十七天(lvs_nat模式与lvs_dr模式配置)
上午 核心:内核中的ipvs,ipvsadm1、安装ipvsadm[rootnat ~]# yum -y install ipvsadm2、配置规则查看所有的规则,如果已经配置好规则,重启之后也就没有了[rootnat ~]# ipvsadm -L -n 1、配置vip网卡 (1)在…...

JAVA中DateFormat详解
在Java中,DateFormat 是一个抽象类,它用于格式化和解析日期。DateFormat 允许进行日期-文本之间的转换,以及日期-时间之间的解析和格式化。Java 提供了多个 DateFormat 的具体子类,其中最常用的是 SimpleDateFormat,它…...

uniapp——列表选择样式
案例 代码 <view class"list"><block v-for"(item,index) in 8" :key"index"><view class"item" click"choosePackage(item)" :class"{active:item current}"><view class"i_money&q…...

解决客户访问超时1s问题
访问公网地址返回状态码499-CSDN博客 需求描述 客户访问公司公网服务,期望在1s内完成。他们在客户端设置了超时1s的配置,如果超过1s公司服务就会报错499,这是正常的请求返回。 这里是业务简易的连路图: 分析问题 目前这个服务通过公网的alb负载均衡到ecs,通过ecs再转发…...

Linux命令(基础面试可用,都是自己觉得平时使用多的)
1.cat 参数:-n:显示行号-s:压缩连续的空行,只显示一个空行2.chattr 改变文件属性 语法:chattr [-RV] [/-/<属性>][文件或目录] 属性:a:让文件或目录仅供附加用途i:不得任意更…...

opencv-python图像增强一:传统图像去噪方法整理
一、简介: 在数字图像处理领域,噪声一直是影响图像质量的重要因素。无论是拍摄过程中的环境干扰,还是传输过程中的信号失真,噪声都可能导致图像模糊、细节丢失,甚至影响后续的图像分析和应用。为了提高图像的视觉效果…...