Junit 单元测试之错误和异常处理

错误和异常处理是测试中非常重要的部分。假设我们有一个服务,该服务从数据库中获取用户。现在,我们要考虑的错误场景是:数据库连接断开。

整体代码示例

首先,为了简化,我们让服务层就是简单的类,然后使用Id查找用户,这个和之前测试UserService接口不太一样哦:

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;public User getUserById(Long id) {return userRepository.findById(id).orElse(null);}
}

现在,我们要模拟UserRepository的行为,使其在尝试获取用户时引发一个异常。这里我们使用Mockito进行模拟:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {//之前我们是定义了一个UserService接口,现在简化成UserService类了哈@InjectMocksprivate UserService userService;@Mockprivate UserRepository userRepository;@Beforepublic void setUp() throws Exception {MockitoAnnotations.initMocks(this);}//重点,后文详解!@Test(expected = DatabaseConnectionException.class)public void testGetUserByIdWithDbError() {when(userRepository.findById(anyLong())).thenThrow(new DatabaseConnectionException("Database connection failed!"));userService.getUserById(1L);}
}//重点,后文详解!
class DatabaseConnectionException extends RuntimeException {public DatabaseConnectionException(String message) {super(message);}
}

在上述测试中,我们模拟了userRepository.findById()方法,使其抛出DatabaseConnectionException异常。然后,我们在测试方法上使用@Test(expected = DatabaseConnectionException.class)来表示我们期望该方法引发此异常

这样,如果getUserById方法在遇到此异常时没有正确处理,测试将失败。这确保了即使在面对意外的数据库问题时,我们的代码仍能按预期的方式运行(在这种情况下,按预期抛出异常)。


到底在模拟什么?到底在测试什么?

下面,我们进一步说明:

  1. 测试目标:这个测试的目标是确保当userRepository.findById()方法抛出DatabaseConnectionException异常时,userService.getUserById()方法也会抛出同样的异常。

  2. 模拟异常:在这行代码中,我们指定了当userRepository.findById()被调用时,它应该抛出DatabaseConnectionException异常。

    when(userRepository.findById(anyLong())).thenThrow(new DatabaseConnectionException("Database connection failed!"));
    
  3. 调用Service方法:接下来,我们调用了userService.getUserById(1L)。我们期望它在内部调用userRepository.findById()(这在实际的UserService实现中应该是这样的)。因此,由于我们已经模拟了userRepository.findById()来抛出异常,所以userService.getUserById()也应该会抛出这个异常。

  4. 验证异常@Test(expected = DatabaseConnectionException.class)注解表示我们期望这个测试方法在执行时会抛出DatabaseConnectionException异常。如果这个方法执行完并没有抛出这个异常,那么测试将会失败。

  5. 测试的目的:这个测试的目的并不是检查userRepository.findById()本身是否真的会抛出异常,而是检查当它抛出异常时,userService.getUserById()是否会正确地传递这个异常。这可以帮助我们确保UserService在处理异常时的行为是正确的。其实本质上来说,抛出异常和预期值的测试逻辑几乎是一样的,都是通过给定下层值,验证上层代码关系。

综上所述,这个测试确保了当底层UserRepository出现数据库连接错误时,上层的UserService可以正确地传递这个错误。这对于后续的异常处理很重要,例如:在Controller层将这个异常转化为一个友好的错误消息返回给用户。


什么时候测试失败?

在正常情况下,只要Service层确实调用了Repository的方法,并且Repository的方法抛出了RuntimeException(或其子类),那么Service层的调用方法也应该会收到并进一步抛出这个异常。

但是,以下几种情况可能导致测试不通过:

  1. 异常被吞没:如果Service层调用了Repository的方法,但内部捕获了该异常并没有重新抛出,那么测试就会失败。例如:

    public User getUserById(Long id) {try {return userRepository.findById(id);} catch (DatabaseConnectionException e) {// 异常被吞没了return null;}
    }
    

  2. 调用的方法不正确:如果Service层没有调用预期的Repository方法,而是调用了其他方法,或者完全没有调用,那么模拟的异常就不会被触发,导致测试失败。

  3. 模拟的不正确:如果在测试中模拟的方法或参数与实际调用的方法或参数不匹配,那么模拟的异常也不会被触发。例如,如果Service实际上是这样调用的:userRepository.findById(2L),但我们的模拟是这样的:when(userRepository.findById(1L))...,那么异常就不会被触发。

    • 其他未预料到的异常:有时可能会有其他的未被预料到的异常被抛出,这也会导致测试失败。

因此,虽然大多数情况下,如果Repository层方法抛出了异常,Service层应该也会抛出,但还是存在一些情况导致测试不通过,这也是进行此类测试的原因。


Exception 异常类定义

class DatabaseConnectionException extends RuntimeException {public DatabaseConnectionException(String message) {super(message);}
}

DatabaseConnectionException是一个自定义的异常类。在Java中,异常是用来表示程序运行中的问题或异常情况的对象。当某些问题发生时,通常会抛出(throw)一个异常。

这里,我们定义了一个继承自RuntimeException的新异常类DatabaseConnectionExceptionRuntimeException是Java中所有非检查型异常的基类。所谓“非检查型”是指编译器不强制我们捕获或声明它。这与Exception(检查型异常)相对。

关于DatabaseConnectionException类的解释:

  1. class DatabaseConnectionException extends RuntimeException - 这表示我们正在定义一个名为DatabaseConnectionException的新类,该类是RuntimeException的子类。这意味着DatabaseConnectionException继承了RuntimeException的所有特性。

  2. public DatabaseConnectionException(String message) - 这是DatabaseConnectionException类的构造方法。当我们创建DatabaseConnectionException的新实例时,可以传递一个消息字符串给这个构造函数。

  3. super(message); - 这行代码调用了父类(RuntimeException)的构造方法,并将message传递给它。这样,当异常被抛出并捕获时,我们可以获取并显示这个消息

这种自定义异常,通常在我们希望为特定的错误情况定义更具描述性的异常名时使用,或者当我们想为特定的异常情况添加更多上下文信息时使用,信息越多,测试反馈的效果越好,所以一般使用自定义异常,继承RuntimeException!下面我们讨论一下,为什么建议使用RuntimeException?


RuntimeException 使用意义

使用RuntimeException(非检查型异常)还是Exception(检查型异常)来自定义数据库异常(或其他异常)是一个设计决策,并且这两者在Java中有不同的含义和用途。

下面是一些选择使用RuntimeException的原因:

  1. 不需要显式处理:当方法中抛出非检查型异常时,调用该方法的代码不需要显式地处理异常(即不需要使用try-catch或在方法签名中使用throws)。这使得代码更简洁,更易读。

  2. 表示编程错误:非检查型异常通常用于表示编程错误,例如空指针异常、数组越界等。对于某些数据库异常,如配置错误,这可能是一个编程错误,因此使用RuntimeException可能更合适。

  3. 强制开发者考虑异常处理策略:使用检查型异常会强制调用者处理异常,这可能会导致过多的try-catch块并使代码复杂化。而使用非检查型异常,开发者可以选择在何处处理异常,这通常会导致更好、更集中的异常处理策略。

  4. 与现有框架兼容:许多现代Java框架,如Spring,倾向于使用非检查型异常,因为它们认为异常应该在应用程序的高层(如Controller或Service)中统一处理。

  5. 灵活性:有时,在开发过程的后期,可能会发现某些异常不再是关键的,不需要强制处理。对于非检查型异常,这意味着不需要修改方法签名或调用代码。

然而,这并不意味着总是应该选择非检查型异常。有时,如果你希望调用者必须处理某个特定的异常,使用检查型异常可能更合适。选择使用哪种异常是基于特定上下文和需求的决策。但在许多现代Java应用程序中,倾向于使用RuntimeException因为它提供了更大的灵活性和简洁性。


总结

模拟异常的目的

  • 验证代码在遇到异常时是否有正确的响应,例如是否抛出了预期的异常
  • 确保代码在异常情况下仍然能够维持预期的状态或行为。
  • 单元测试通常关注隔离性,因此模拟异常可以确保在不涉及实际外部依赖的情况下,模拟各种可能的场景。

真正的数据库异常是不是Runtime异常

在Java中,数据库操作可能会抛出多种异常。其中,SQLException 是一个受检异常(checked exception)。

但在很多现代的框架中(如Spring),这些受检异常通常会被转换成运行时异常(runtime exceptions),这样可以使代码更为简洁,避免了过多的try-catch块。

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

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

相关文章

[Machine Learning][Part 6]Cost Function代价函数和梯度正则化

目录 拟合 欠拟合 过拟合 正确的拟合 解决过拟合的方法:正则化 线性回归模型和逻辑回归模型都存在欠拟合和过拟合的情况。 拟合 来自百度的解释: 数据拟合又称曲线拟合,俗称拉曲线,是一种把现有数据透过数学方法来代入一条…

浅谈大数据之Flink-2

1.5 流处理基础概念 在某些场景下,流处理打破了批处理的一些局限。Flink作为一款以流处理见长的大数据引擎,相比其他流处理引擎具有众多优势。本节将对流处理的一些基本概念进行细化,这些概念是入门流处理的必备基础,至此你将正式进入数据流的世界。 1.5.1 延迟和吞吐 …

PHP框架开发实践 | 1024 程序员节:通过index.php找到对应的controller是如何实现的

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年6月CSDN上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师…

STM32CubeMX之DMA辅助串口数据接收

1.DMA辅助串口数据接收 1.1 DMA简介 直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。   两个DMA控制器有12个通道(DMA1有7个通道&am…

什么是网络爬虫,爬虫的机制是那些

网络爬虫(也称为网页蜘蛛、网络机器人或网页追逐者)是一种按照预设规则,自动抓取万维网信息的程序或脚本。它们广泛应用于搜索引擎、数据挖掘、竞争情报、价格监测等各种互联网应用中。 爬虫机制是爬虫程序或机器人用来访问、抓取、索引以及…

前端Javascript | 数组值随机选择函数

文章目录 目的解决方案 目的 为了解决 postman 传参数据定制化,需要写一点前置脚本,有用到随机选取数组中的值来造数据。 解决方案 // 随机数组函数 function getRandomNumber(arr) {return arr[Math.floor(Math.random() * arr.length)]; }

2023年中国清净剂行业需求现状及前景分析[图]

清净剂用于中和由于燃烧和润滑油氧化产生的酸性物质,并清除颗粒和污物。这类杂质在油中的溶解度有限,因此,清净剂可以最大程度减少沉积物的生成,降低污染,提高环保排放标准。成熟产品有磺酸盐、硫化烷基酚盐、烷基水杨…

yolov作者简介

作者简介 作者叫Joseph Redmon,在谷歌学术上搜索作者的简介。 地址:‪Joseph Redmon‬ -巨人学术搜索‬‬ (cljtscd.com) 他提出了最著名的YOLO算法。其中YOLOV1的引用量达到了40287次。 gitihub地址:github地址 主页:个人主页

workerman 运行时报错 Call to undefined function posix_getpid()

使用 验证php扩展是否齐全 curl -Ss https://www.workerman.net/check | php缺少posix 下载 在 Linux 系统上,可以使用包管理器来安装 php-posix 扩展,例如 Ubuntu 系统可以通过以下命令进行安装: sudo apt-get install php-posix如果你使用…

LeetCode 2895. 最小处理时间【贪心,排序】1351

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

ims-ui项目搭建

node版本: npm版本: 创建vite项目: npm create vitelatest 使用的vite版本为: 安装router4,安装命令如下: npm install vue-router4 安装pinia,安装命令如下: npm install pinia 安装Pinia持…

【Linux】adduser命令使用

我们经常在linux系统中创建用户。有时候用的是 useradd 有时候用的是 adduser ,好混乱啊到底用哪个啊。今天咱们一起来学习一下。 adduser与useradd的区别 useradd 命令是内置的 Linux 命令,在任何 Linux 系统中都可用。然而,使用这种低级…

ssm+vue基本微信小程序的今日菜谱系统

项目介绍 谈到外出就餐,我们除了怕排队,也怕这家餐厅的服务员不够用,没人为我们点餐,那么一餐饭排队一小时,点餐恐怕也要花个半小时,这样不仅给消费者的用餐体验大打折扣同时也给商家的口碑造成了严重负面…

代码覆盖率统计Super-jacoco在公司级容器化项目中的具体应用方案

目录 一、介绍 二、自己在本地搭建Super-jacoco服务 2.1 准备工作 2.2 部署super jacoco服务 1、下载super jacoco 项目 2、初始化数据库 3、配置application.properties 4、编译super jacoco项目 5、部署 super jacoco 服务 2.3 启动被测项目 2.4、代码覆盖率收集 2…

mac虚拟机安装homebrew时的问题

安装了mac虚拟机&#xff0c;结果在需要通过“brew install svn”安装svn时&#xff0c;才注意到没有下载安装homebrew。 于是便想着先安装homebrew&#xff0c;网上查的教程大多是通过类似以下命令 “ruby <(curl -fsSkL raw.github.com/mxcl/homebrew/go)” 但是都会出现…

RFID智能制造应用:助力企业提升制造效率!

随着企业间竞争加剧&#xff0c;如何提升企业生产效率&#xff0c;降低成本成为不少制造企业持续追求的目标。利用智能制造中的RFID设备&#xff0c;可以对企业入库、盘点、生产、出库等流程进行监控&#xff0c;本文将探讨智能制造中的RFID设备如何帮助企业提升制造效率&#…

UE修改千年数据库的地址

UE打开testdb 搜索绝对地址按ctrlg 0x090h 3 4组为性别 0x090h 9组 为人物当前所在地图代码 0x090h 10 11 12 13组为人物所在地图坐标 10 11为x坐标 12 13为y坐标 后三组未知 从绝对地址0x000a0h第10组---0x00120h第9组 为人物的属性 包括一些不能看到的隐藏属性 例如千年1中的…

【C/C++笔试练习】内联函数、缺省参数、函数重载、类定义、不要二、字符串转成整数、Fibonacci数列、合法括号序列判断

文章目录 C/C笔试练习1.内联函数&#xff08;1&#xff09;内联函数的使用&#xff08;2&#xff09;内联函数的使用 2.缺省参数&#xff08;3&#xff09;缺省参数概念理解 3.函数重载&#xff08;4&#xff09;函数重载的定义&#xff08;5&#xff09;函数重载的定义 4.类定…

Flutter ☞ 变量

在Flutter中&#xff0c;变量分为两种类型 弱类型强类型 弱类型 var 如果没有初始值&#xff0c;可以变成任何类型 var a; // var a ; // 一旦赋值&#xff0c;就确定类型&#xff0c;不能随意改动 a abc; a 123; a true; a {key: 123}; a [abc];print(a)Object 动…

python+selenium自动化测试环境搭建步骤(selenium环境搭建)

一、自动化简介 1.自动化测试概念&#xff1a; 是把以人为驱动的测试转化为机器执行的一种过程&#xff0c;它是一种以程序测试程序的过程 2.自动化测试分类&#xff1a; 一般IT上所说的自动化测试是指功能自动化测试&#xff0c;通过编码的方式用一段程序来测试一个软件的…