一文说透Springboot单元测试

你好,我是柳岸花开。

一、单元测试说明

1 单元测试的优点与基本原则

一个好的单元测试应该具备以下FIRST 原则和AIR原则中的任何一条:

单元测试的FIRST 规则

Fast 快速原则,测试的速度要比较快,

Independent 独立原则,每个测试用例应该互不影响,不依赖于外部资源。

Repeatable 可重复原则,同一个测试用例多次运行的结果应该是相同的

Self-validating 自我验证原则,单元测试可以自动验证,并不需要手工干预

Thorough 及时原则 单元测试必须即使进行编写,更新,维护。保证测试用例随着业务动态变化

AIR原则

Automatic 自动化原则 单元测试应该是自动运行,自动校验,自动给出结果。

Independent 独立原则 单元测试应该独立运行,吧相互之间无依赖,对外无依赖,多次运行之间无依赖。

Repeatable 可重复原则 单元测试是可重复运动的,每次的结果都稳定可靠。

一个整套完善的单元测试可以保障后续的增添功能时,程序迭代过程中,代码的逻辑正确性。验证程序的输入和输出与最初设计一致。这对后续的集成测试等会提供巨大的帮助。同时也会有利于集成测试的顺利进行。

2 单元测试的粒度应该如何选择

关于单元测试的粒度,可以总结以下几点:

DAO层的单元测试: 对于基本CRUD,可以考虑跳过这一部分单元测试,而一些比较复杂的动态更新、查询等操作,建议用使用H2去做模拟单元测试。

Service层的单元测试:基本上一个Service里面肯定会依赖很多其他的service(此处也建议将成员变量通过构造方法进行注入,以便于单元测试去Mock),此时建议我们将依赖其他service的方法用Mock替代,Service里面的一些数据库的操作也进行Mock。这样可以保证service测试的独立性,不过对于逻辑复杂的方法可能要花很多时间在Mock上面。 如果发现需要Mock的方法过多,那么可能就需要考虑将要测试的方法是不是需要重构。

Controller(API)层的单元测试:主要着重测试HTTP status在 200,400,500 等情况下的异常处理,request及response的转换等。由于其余部分的代码测试都已经在其对应的单元测试覆盖,那么此时可以Mock绝大部分Serivce层中的方法。

一般工具类的单元测试:一些工具类里面包含了比较多的逻辑,所以需要尽可能考虑多种情况下测试用例。

How to write good tests · mockito/mockito Wiki · GitHub

二、引入单元测试组件

pom.xml

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-surefire-plugin</artifactId>

</plugin>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-failsafe-plugin</artifactId>

</plugin>

</plugins>

</build>

三、编写单元测试用例

1 JUnit 5 核心API

JUnit 5 官网

JUnit 5 User Guide

2 Mockito

2.1 简介

Mockito 是当前最流行的 单元测试 Mock 框架。采用 Mock 框架,我们可以 虚拟 出一个 外部依赖,降低测试 组件 之间的 耦合度,只注重代码的 流程与结果,真正地实现测试目的。

代码示例


public class ArticleManager {

private ArticleDatabase database;

private ArticleCalculator calculator;

private UserProvider userProvider;

....

}

@ExtendWith({MockitoExtension.class})

public class ArticleManagerTest extends SampleBaseTestCase 
{

@Mock

private ArticleCalculator calculator;

// note the mock name attribute

@Mock(name = "database")

private ArticleDatabase dbMock;

@Spy

private UserProvider userProvider = new ConsumerUserProvider();

@InjectMocks

private ArticleManager manager;

@Test

public void shouldDoSomething() {

//mock method behave for @Mock Object

when(calculator.someMethod(anyInt())).thenReturn("A value");

when(dbMock.someMethod(anyInt())).thenReturn("A value");

//mock method behave for @Spy Object

doReturn("foo").when(userProvider).getById(100001);

//call target test method 并断言执行结果

assertEquals(1,manager.initiateArticle());

// 验证Mock的方法被调用到了

verify(userProvider).getById(100001);

}

}

官方说明及文档:

Mockito framework site

doc:Mockito - mockito-core 5.6.0 javadoc

2.2 核心API

1 Mockito.mock() / @Mock: 创建 mock 对象


//Let's import Mockito statically so that the code looks clearer

import static org.mockito.Mockito.*;

//mock creation

List mockedList = mock(List.class);

//using mock object

mockedList.add("one");

mockedList.clear();

//verification

verify(mockedList).add("one");

verify(mockedList).clear();

**推荐使用@Mock注解方式**

public class ArticleManagerTest {

@Mock

private ArticleCalculator calculator;

@Mock

private ArticleDatabase database;

@Mock

private UserProvider userProvider;

}

2 Mockito.when() / BDDMockito.given() mock 方法结果

具体用法,看这个两个类上的注释

3 Mockito.spy() / @Spy 仿造真实对象的部分方法

具体说明及用法看 Mockito.spy() 方法的注释

4 @InjectMocks 将 @Moc @Spy 仿造的对象注入到被测试对象

具体用法,看InjectMocks类上的注释

注意:

有些依赖没法用@InjectMocks来自动注入,可以通过引入ReflectionTestUtils,解决依赖注入的问题。

ReflectionTestUtils.setField(controller,"service",service);

5 Mockito.verify() 验证仿造行为被执行

verify系列方法注释

3 spring test

Testing :: Spring Framework

为基于spring框架开发的应用的测试提供了一系列工具封装,让我们能更简便编写测试用例。

3.1 Unit Testing

Unit Testing :: Spring Framework

真正的单元测试通常运行得非常快,因为不需要设置运行时基础设施。强调真正的单元测试作为开发方法的一部分可以提高您的生产力。您可能不需要测试章节的这一部分来帮助您为基于ioc的应用程序编写有效的单元测试。然而,对于某些单元测试场景,Spring框架提供了模拟对象和测试支持类,这些将在本章中描述

Mock Objects

Spring包含许多专门用于mock的包,模拟这些对象:

Environment

JNDI

Servlet API

Spring Web Reactive

测试支持类

AopTestUtils 用于获取被代理对象

ReflectionTestUtils 用于对 非 public的属性等设置值

TestSocketUtils is a simple utility for finding available TCP ports on localhost for use in integration testing scenarios.

3.2 Integration Testing

Spring集成测试模块的目标

Spring的集成测试支持有以下主要目标:

To manage Spring IoC container caching between tests.

To provide Dependency Injection of test fixture instances.

To provide transaction management appropriate to integration testing.

To supply Spring-specific base classes that assist developers in writing integration tests.

管理测试之间的Spring IoC容器缓存。

提供测试fixture实例的依赖注入。

提供适合集成测试的事务管理。

提供特定于spring的基类,帮助开发人员编写集成测试。

Context Management and Caching

Spring TestContext框架提供了Spring ApplicationContext实例和WebApplicationContext实例的一致加载,以及这些上下文的缓存。对加载上下文的缓存支持很重要,因为启动时间可能成为一个问题。

Dependency Injection of Test Fixtures 测试装置的依赖注入

当TestContext框架加载应用程序上下文时,它可以通过使用依赖注入选择性地配置测试类的实例。这提供了一种方便的机制,可以通过使用应用程序上下文中的预配置bean来设置测试fixture。这里的一个强大的好处是,您可以跨各种测试场景重用应用程序上下文(例如,用于配置spring管理的对象图、事务代理、数据源实例等),从而避免了为单个测试用例复制复杂测试fixture设置的需要。

Transaction Management

在访问真实数据库的测试中,一个常见的问题是它们对持久性存储状态的影响。即使在使用开发数据库时,对状态的更改也可能影响将来的测试。此外,许多操作(例如插入或修改持久数据)不能在事务之外执行(或验证)。

TestContext框架解决了这个问题。默认情况下,框架为每个测试创建并回滚一个事务。您可以编写可以假定存在事务的代码。如果在测试中调用事务代理对象,则根据其配置的事务语义,它们的行为正确。另外,如果一个测试方法在为测试管理的事务中运行时删除了所选表的内容,那么该事务在默认情况下回滚,并且数据库返回到执行测试之前的状态。事务性支持是通过使用在测试的应用程序上下文中定义的PlatformTransactionManager bean提供给测试的。

如果您想要提交事务(不常见,但在您想要填充或修改数据库的特定测试时偶尔有用),您可以告诉TestContext框架使用@Commit注释导致事务提交,而不是回滚。

3.3 JDBC Testing Support

JDBC 测试支持提供了如下两种:

JdbcTestUtils工具类

JdbcTestUtils provides the following static utility methods.

countRowsInTable(..): Counts the number of rows in the given table.

countRowsInTableWhere(..): Counts the number of rows in the given table by using the provided WHERE clause.

deleteFromTables(..): Deletes all rows from the specified tables.

deleteFromTableWhere(..): Deletes rows from the given table by using the provided WHERE clause.

dropTables(..): Drops the specified tables.

Embedded Databases

The spring-jdbc module provides support for configuring and launching an embedded database, which you can use in integration tests that interact with a database. For details, see Embedded Database Support and Testing Data Access Logic with an Embedded Database.

👇关注我,下期了解👇 ​ SpringMVC源码 ​ ​ alt ​ ​ 回复 222,获取Java面试题合集 ​ 关于我 ​ 一枚爱折腾的Java程序猿,专注Spring干货。把路上的问题记录下来,帮助那些和我一样的人。 ​ 好奇心强,喜欢并深入研究古天文。 ​ 崇尚 个人系统创建,做一些时间越长越有价值的事情。思考 把时间留下来 又 每刻都是新的。

本文由 mdnice 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt 多窗体、复用窗口的使用

1.继承自QWidge的窗口的呈现&#xff0c;作为tabPage呈现&#xff0c;作为独立窗口呈现 2.继承自QMainWindow的窗口的呈现&#xff0c;作为abPage呈现&#xff0c;作为独立窗口呈现 1. 继承自QWidge的窗口的呈现 1.1 作为tabPage呈现 void MutiWindowExample::on_actWidgetI…

AI绘画入门实践|Midjourney 提示词的使用技巧

提示词长短 尽可能做到简洁明了。 提示词很短 MJ 出图的随机性更高&#xff0c;创造的内容更有想象力&#xff0c;更适合创意发散的图像生成。 a dog 提示词很长 MJ 出图会更加精准&#xff0c;但描述太过详细&#xff0c;有可能出现AI理解不到位的情况。 越到后面的提示词&…

风险评估:IIS的安全配置,IIS安全基线检查加固

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 这一章节我们需…

Java面试八股之Redis集群Cluster

Redis集群Cluster Redis Cluster是一种基于数据分片&#xff08;Sharding&#xff09;的分布式缓存和存储系统&#xff0c;它实现了数据的水平扩展、高可用性和自动故障转移。以下是对Redis Cluster模式详细实现流程的描述&#xff1a; 1. 初始化与配置 部署节点&#xff1a…

flutter 手写 TabBar

前言&#xff1a; 这几天在使用 flutter TabBar 的时候 我们的设计给我提了一个需求&#xff1a; 如下 Tabbar 第一个元素 左对齐&#xff0c;试了下TabBar 的配置&#xff0c;无法实现这个需求&#xff0c;他的 配置是针对所有元素的。而且 这个 TabBar 下面的 滑块在移动的时…

产品经理-产品经理会在项目中遇到的几个问题(16)

项目中遇到了需求变更怎么办&#xff1f; 首先要弄清楚需求变更的原因是什么。如果是因为在迭代的过程中更好地理解了用户需求 进而产生了更好的需求则完全是正常的。如果是因为老板的需求 那就需要和老板沟通清楚&#xff0c;并且确保自己能理解老板的需求&#xff0c;而且这个…

软件测试——测试用例

工作职责&#xff1a; 1.负责产品系统测试&#xff0c;包括功能测试、性能测试、稳定性测试、用户场景测试、可靠性测试等。 2.负责测试相关文档的编写&#xff0c;包括测试计划、测试用例、测试报告等。 3.负责自动化测试框架、用例的维护。 岗位要求&#xff1a; 1.熟练…

800块,我从淘宝上买AGV……

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 从淘宝上打算够购买一台AGV小车&#xff0c;上去一搜&#xff0c;嘿&#xff0c;你别说&#xff0c;还真有。便宜的才200块钱。 很兴奋把…

17-8 向量数据库之野望8 - 7 个主流向量数据库

​​​​​​ 在快速发展的人工智能 (AI)、机器学习 (ML) 和数据工程领域,对高效数据存储和检索系统的需求至关重要。矢量数据库已成为管理这些技术通常依赖的复杂高维数据的关键解决方案。在这里,我们探讨了每个 AI/ML/数据工程师都应该熟悉的七个矢量数据库,重点介绍了它们…

【Linux】01.Linux 的常见指令

1. ls 指令 语法&#xff1a;ls [选项] [目录名或文件名] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息 常用选项&#xff1a; -a&#xff1a;列出当前目录下的所有文件&#xff0c;包含隐藏文件…

JavaSE学习笔记第三弹之异常抛出

今天我们继续来学习JavaSE相关的知识&#xff0c;希望与大家共同努力。 目录 异常 什么是异常 运行时异常 编译时异常 ​编辑 为什么需要异常处理机制 错误 异常的处理与抛出 异常处理 异常抛出 自定义异常 结语 异常 什么是异常 Java中异常是一种在程序运行时发…

Java二十三种设计模式-原型模式(5/23)

Java中的原型模式&#xff1a;深入解析与应用实践 引言 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它使用一个已有的对象作为原型&#xff0c;通过复制这个原型来创建新的实例。这种模式适用于对象的创建成本较高&#xff0c;或者对…

BL201分布式I/O耦合器连接Profinet网络

钡铼技术的BL201分布式I/O耦合器是一个用于Profinet网络的设备&#xff0c;用于连接远程输入/输出&#xff08;I/O&#xff09;设备到控制系统&#xff0c;如可编程逻辑控制器&#xff08;PLC&#xff09;&#xff0c;能够实现分布式的I/O连接和通信。 它支持标准Profinet IO …

Qt实现一个简单的视频播放器

目录 1 工程配置 1.1 创建新工程 1.2 ui界面配置 1.3 .pro配置 2 代码 2.1 main.c代码 2.2 widget.c 2.3 widget.h 本文主要记述了如何使用Qt编写一个简单的视频播放器&#xff0c;整个示例采用Qt自带组件就可以完成。可以实现视频的播放和暂停等功能。 1 工程配置 1.…

都说油车不行了,外资两款经典油车降价起量,与电动车展开决战

6月份的轿车销量数据出来了&#xff0c;数据显示电动汽车并没稳赢&#xff0c;燃油车终于发力了&#xff0c;反过来围剿电动汽车了&#xff0c;显示出消费者并非说不买油车&#xff0c;而是看价格&#xff0c;价格合适&#xff0c;消费者仍然会选择油车。 6月份的数据显示之前销…

AV1 编码标准帧间预测技术概述

AV1 编码标准帧间预测 AV1&#xff08;AOMedia Video1&#xff09;是一种开源的视频编码格式&#xff0c;它在帧间预测技术上做出了显著的改进和扩展&#xff0c;以提供比现有标准更高的压缩效率和更好的视频质量。以下是AV1帧间预测技术的几个关键点&#xff1a; 参考帧扩展&a…

VGMP(VRRP组管理协议)和HRP(华为冗余技术)

1、VGMP VGMP&#xff08;VRRP Group Management Protocol&#xff09;&#xff1a;VRRP组管理协议&#xff0c;是华为开发的一种私有协议&#xff0c;主要用于实现对多个VRRP组进行统一管理的功能 概述&#xff1a;VGMP协议是在VRRP协议的基础上开发的&#xff0c;其最主要的…

linux的学习(五):shell编程中的变量,运算符,条件判断

简介&#xff1a; shell编程的基本概念&#xff0c;定义变量&#xff0c;运算符&#xff0c;条件判断的基本使用 shell编程 把多个命令写到一个文件里&#xff0c;这个文件就是脚本&#xff0c;里面还有很多的流程控制 基本概念 脚本的后缀名是.sh 脚本的执行&#xff1a;…

Golang | Leetcode Golang题解之第235题二叉搜索树的最近公共祖先

题目&#xff1a; 题解&#xff1a; func lowestCommonAncestor(root, p, q *TreeNode) (ancestor *TreeNode) {ancestor rootfor {if p.Val < ancestor.Val && q.Val < ancestor.Val {ancestor ancestor.Left} else if p.Val > ancestor.Val && q…

【Docker】Docker 的数据管理与镜像创建

目录 一.数据管理 1.数据卷 2.数据卷容器 二.端口映射 三.容器互联 四.Docker 镜像的创建 1.基于现有镜像创建 1.1.首先启动一个镜像&#xff0c;基于镜像创建容器&#xff0c;更新容器内容 1.2.将修改后的容器提交为新的镜像&#xff0c;需要使用该容器的 ID 号创建新…