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

这篇具有很好参考价值的文章主要介绍了Junit 单元测试之错误和异常处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

整体代码示例

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

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

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

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

//之前我们是定义了一个UserService接口,现在简化成UserService类了哈
    @InjectMocks
    private UserService userService;

    @Mock
    private UserRepository userRepository;

    @Before
    public 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块。文章来源地址https://www.toymoban.com/news/detail-717354.html

到了这里,关于Junit 单元测试之错误和异常处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux下安装junit,并通过命令执行junit单元测试

    1.首先你的Linux下应该已经安装了jdk,如果没有安装,请先查询安装,并配置好环境变量 2.首先找到你的jdk安装目录,如果你已经配置好环境变量的话,可以执行下面的语句:   博主的安装目录是/usr/lib/jvm/temurin-11-jdk-amd64/ 3.下载junit-4.11.jar和hamcrest-core-1.3.jar到这个目录里的l

    2024年02月04日
    浏览(43)
  • 单元测试 —— JUnit 5 参数化测试

    目录 设置 我们的第一个参数化测试 参数来源 @ValueSource @NullSource @EmptySource @MethodSource @CsvSource @CsvFileSource @EnumSource @ArgumentsSource 参数转换 参数聚合 奖励 总结 如果您正在阅读这篇文章,说明您已经熟悉了JUnit。让我为您概括一下JUnit——在软件开发中,我们开发人员编写的代

    2024年02月03日
    浏览(44)
  • 软件测试实验:Junit单元测试

    目录 前言 实验目的 实验内容 实验要求 实验过程 题目一 题目一测试结果 题目二 题目二实验结果 总结 软件测试是软件开发过程中不可缺少的一个环节,它可以保证软件的质量和功能,发现并修复软件的缺陷和错误。软件测试分为多种类型,其中一种是单元测试,即对软件

    2024年02月02日
    浏览(49)
  • 使用JUnit单元测试

    前提:   测试题目: 根据输入的年份和月份判断月份的天数。若月份不在有效范围之内,应提示:“月份输入不正确。”。月份不为2月,根据输入月份输出对应的月份天数。月份为2月,根据年份判断如为普通闰年,输出2月份正确天数;如为世纪闰年,输出2月份正确天数;

    2024年02月04日
    浏览(33)
  • JUnit 5 单元测试框架

    依赖安装 所有支持的注解都在包 org.junit.jupiter.api 下。 基本使用:

    2024年01月20日
    浏览(44)
  • Junit单元测试(笔记)

    Junit是一个Java语言的单元测试框架,简单理解为可以用于取代java的( 部分 )main方法。Junit属于 第三方 工具,需要 导入jar包 后使用。 a.在当前模块下创建lib文件夹 b.把junit的jar包,拷贝到lib的文件夹中 c.把jar包添加到图书馆中 执行的结果: 备注: Junit常用注解(Junit5.x版本) @Befor

    2023年04月26日
    浏览(37)
  • 单元测试及其工具Junit

    单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确,通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。 单元测试是软件测试的一种类型,测试对象是最基础的代码单元(函数、类、模块),属

    2024年02月10日
    浏览(40)
  • spring 单元测试 Junit

    我是南城余!阿里云开发者平台专家博士证书获得者! 欢迎关注我的博客!一同成长! 一名从事运维开发的worker,记录分享学习。 专注于AI,运维开发,windows Linux 系统领域的分享! 本章节对应知识库 https://www.yuque.com/nanchengcyu/java 本内容来自尚硅谷课程,此处在知识库做了

    2024年02月04日
    浏览(64)
  • Junit 单元测试(详解)

    本博主将用CSDN记录软件开发求学之路上亲身所得与所学的心得与知识,有兴趣的小伙伴可以关注博主!也许一个人独行,可以走的很快,但是一群人结伴而行,才能走的更远! JUnit 是一种流行的单元测试框架,它可以帮助开发人员编写、运行和调试单元测试,以确保代码的

    2023年04月15日
    浏览(35)
  • 单元测试junit

    官网:https://junit.org/ JUnit是一个Java语言的单元测试框架,Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何完成功能和完成什么样的功能,Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了 测试方法必须使用 @Test 修饰 测试方法必须使用

    2024年02月10日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包