当前位置: 首页 > news >正文

【JUnit实战3_28】第十七章:用 JUnit 5 实测 SpringBoot 项目

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
有了前一章对 Spring 框架依赖注入机制的深入剖析,本章讲解 SpringBoot 框架就显得格外轻松了。同样一个案例,改造为 SpringBoot 项目后需要配置的注解都少了很多,甚至都不需要引入 application-context.xml 文件了;再配合强大的 IDEA 辅助功能,可以真正让开发者聚焦业务逻辑或测试逻辑,而不用过分关注单元测试的环境搭建,非常省心。

第十七章:测试 Spring Boot 应用

本章概要

  • 利用 Spring Initializr 创建项目的方法;
  • 将集成了 JUnit 5Spring 项目迁移为 Spring Boot 项目的方法;
  • Spring Boot 中实现一个测试专用的配置组件;
  • spring Boot + JUnit 5 实战案例演示。

Working with Spring Boot is like pair-programming with the Spring developers.
用了 Spring Boot 就像在和 Spring 开发者进行结对编程。

—— 佚名

17.1 SpringBoot 简介

Spring Boot 是一个基于 Spring 框架践行 约定优于配置(convention-over-configuration 原则的成功案例。它极大地减少了初始化 Spring 应用的繁琐配置工作。

上一章提到,Spring 框架通过控制反转和依赖注入,本意是让开发者专注于业务逻辑的实现,但 Spring 繁琐的 XML 配置细节让初学者望而生畏,适得其反。Spring Boot 就是为了简化配置诞生的:大量的默认配置和注解驱动的风格让开发者真正实现了专注于业务本身的开发,很少再花精力去耐心钻研 XML 配置了。

17.2 用 Spring Initializr 创建项目

时隔六年,通过 SpringBoot 官网的 https://start.spring.io/ 创建项目已然成为 Spring 项目初始化的基础操作。实测时官方已发布了SpringBoot 4.0.0 版框架,默认从 v3.5.7 起步。实测页面截图如下:

Fig17.1

点击 EXPLORE CTRL + SPACE 按钮看到如下 pom.xml 默认配置:

Fig17.2

点击 DOWNLOAD CTRL + ⏎ 按钮就能以下载该项目的 zip 格式压缩包。解压到本地指定位置后导入 IDEA 的实测效果如下:

Fig17.3

17.3 从 Spring 迁移到 Spring Boot

沿用上一章最后演示的观察者模式案例,将实体类、乘客注册相关的主客体、以及事件对象复制到 SpringBoot 对应的包下:

Fig17.4

注意:在迁移 application-context.xml 时无需自动扫描指定包路的元素节点(即 <context:component-scan base-package="..." />),因为 SpringBoot 提供了 @EnableAutoConfiguration 注解:

@SpringBootTest
@EnableAutoConfiguration
@ImportResource("classpath:application-context.xml")
class RegistrationTest {@Autowiredprivate Passenger passenger;@Autowiredprivate RegistrationManager registrationManager;@Testvoid testPersonRegistration() {registrationManager.getApplicationContext().publishEvent(new PassengerRegistrationEvent(passenger));System.out.println("After registering:");System.out.println(passenger);assertTrue(passenger.isRegistered());}
}

实测运行结果(迁移成功):

Fig17.5

17.4 SpringBoot 测试专用配置组件的用法

上一章之所以用 XML 配置 Bean 的定义和组合关系主要有两个目的:

  • 一是方便理解 IoC 机制的原理;
  • 二是无需修改源代码且无需重新编译,仅修改 XML 配置即可变更 Passenger 的实现。

既然已经迁移到了 SpringBoot 项目,而 SpringBoot 又新增了 @TestConfiguration 注解来专门处理仅用于测试的 Bean 的实例化,因此可以替换掉原来的 application-context.xml 文件。

具体分两步:

  • 创建一个配置类重新定义 XML 中的两个 Bean 对象,并在类上添加 @TestConfiguration 注解;
  • 在测试类中通过 @Import 注解引入该配置类。

等效的配置类 TestBeans 如下:

@TestConfiguration
public class TestBeans {@Beanpublic Passenger createPassenger(){Passenger passenger = new Passenger("John Smith");passenger.setCountry(createCountry());passenger.setIsRegistered(false);return passenger;}@Beanpublic Country createCountry(){return new Country("USA", "US");}
}

然后将上述配置类导入测试类 RegistrationTest 中,同时删除 @EnableAutoConfiguration@ImportResource("classpath:application-context.xml") 注解:

@SpringBootTest
@Import(TestBeans.class)
class RegistrationTest {@Autowiredprivate Passenger passenger;@Autowiredprivate RegistrationManager registrationManager;@Testvoid testPersonRegistration() {registrationManager.getApplicationContext().publishEvent(new PassengerRegistrationEvent(passenger));System.out.println("After registering:");System.out.println(passenger);assertTrue(passenger.isRegistered());}
}

通过配置类定义需要的 Bean,这种做法更加符合 SpringBoot 的设计理念,同时也强化了编译阶段的类型检测,减少了配置 XML 出错的几率。此外,通过配置类来定义 Bean 还可以获得 IDEA 的内置支持。

例如观察者和被观察者的快速定位——

从被观察者定位到观察者(鼠标悬停会出现 Go to event listener 提示语):

Fig17.6

从观察者(observer)回到被观察者(subject)(鼠标悬停会出现 Go to event publisher 提示语):

Fig17.7

最终的实测结果:

Fig17.8

注意:关于 Bean 的默认名称及修改方法

@AutoWired 注解在实例化 passenger 的过程中,首先按 Bean 的类型 Passenger 进行匹配,然后是名称(默认为 createPassenger)。如果从应用上下文手动获取名称为 "passenger"Bean 是会报错的:

@Test
@DisplayName("should throw exception if invoked via the name passenger")
void shouldThrowExceptionIfInvokedViaTheNamePassenger() {final NoSuchBeanDefinitionException ex = assertThrows(NoSuchBeanDefinitionException.class, () -> {final ApplicationContext context = registrationManager.getApplicationContext();final Passenger passenger1 = context.getBean("passenger", Passenger.class);System.out.println("Bean injected by name 'passenger': " + passenger1);});assertTrue("No bean named 'passenger' available".contains(ex.getMessage()));
}

运行结果:

Fig17.9

如果非要用 "passenger" 作名称,可以将 @Bean 注解改为 @Bean("passenger")L1):

@Bean("passenger")
public Passenger createPassenger(){Passenger passenger = new Passenger("John Smith");passenger.setCountry(createCountry());passenger.setIsRegistered(false);return passenger;
}

这样就能拿到 passenger 对象了:

Fig17.10

17.5 SpringBoot 实战:所有乘客注册事件批量响应

本例在上一章 Spring 实战案例的基础上引入了航班实体类 Flight

public class Flight {private final String flightNumber;private final int seats;private final Set<Passenger> passengers = new HashSet<>();public Flight(String flightNumber, int seats) {this.flightNumber = flightNumber;this.seats = seats;}public String getFlightNumber() {return flightNumber;}public int getSeats() {return seats;}public Set<Passenger> getPassengers() {return Collections.unmodifiableSet(passengers);}public boolean addPassenger(Passenger passenger) {if (passengers.size() >= seats) {throw new RuntimeException("Cannot add more passengers than the capacity of the flight!");}return passengers.add(passenger);}public boolean removePassenger(Passenger passenger) {return passengers.remove(passenger);}@Overridepublic String toString() {return "Flight " + getFlightNumber();}
}

本例的测试目标:对某航班内所有注册成功的乘客,都各自反馈一条应答消息,告知乘客已注册成功。

该航班的乘客信息来自一个 CSV 格式文件 src/test/resources/flights_information.csv。利用 SpringBoot 提供的测试专用的配置注解 @TestConfiguration,可以创建一个配置类 FlightBuilder 来读取文件中的乘客信息,进而在 Spring 容器中定义一个航班实例:

@TestConfiguration
public class FlightBuilder {private static final Map<String, Country> countries = new HashMap<>(){{put("AU", new Country("Australia", "AU"));put("US", new Country("USA", "US"));put("UK", new Country("United Kingdom", "UK"));}};@Bean("flight")Flight buildFlightFromCsv() throws IOException {Flight flight = new Flight("AA1234", 20);try(BufferedReader br = new BufferedReader(new FileReader("src/test/resources/flights_information.csv"))) {String line;do {line = br.readLine();if(line != null) {String[] fields = line.split(";");Passenger p = new Passenger(fields[0].trim());Country c = countries.get(fields[1].trim());p.setCountry(c);p.setIsRegistered(false);flight.addPassenger(p);}} while (line != null);}return flight;}
}

最后,通过 @AutoWired 注解向测试类中注入依赖,并完成航班中已注册乘客的批量信息反馈:

@SpringBootTest
@Import(FlightBuilder.class)
public class FlightTest {@Autowiredprivate Flight flight;@Autowiredprivate RegistrationManager registrationManager;@Testvoid flightTest() {ApplicationContext ctx = registrationManager.getApplicationContext();flight.getPassengers().parallelStream().peek(passenger -> assertFalse(passenger.isRegistered())).forEach(passenger -> ctx.publishEvent(new PassengerRegistrationEvent(passenger)));System.out.println("All passengers from the flight are now confirmed as registered");flight.getPassengers().parallelStream().forEach(passenger -> assertTrue(passenger.isRegistered()));}
}

实测结果:

Fig17.11

后话
本章新知识点似乎并不算多,原因主要有两个:一是作者只重点介绍了和测试相关的 SpringBoot 的相关注解;二是想从 SpringSpringBoot 在同一案例的对比上突出后者在 约定优于配置 方面取得的成效。其实真正的重头戏是下一章将会介绍的、采用 REST 风格开发的 Java 后端项目的测试。敬请关注!

http://www.dtcms.com/a/569118.html

相关文章:

  • 摆脱局域网束缚!用 DS File+cpolar 让 NAS 文件随用随取
  • 网站制作费电力行业做的好的招投标网站
  • 百度网址大全网站大全石家庄网页设计培训班
  • 设计模式学习(十二)状态模式
  • shell-基于k8s/docker管理容器、监控模型训练所消耗的最大CPU与最大内存脚本
  • Maven打包时指定输出路径、以时间戳命名包名和路径名,结合IDEA以指令脚本方式动态配置输出目录
  • PortSwigger
  • Doxygen入门指南:从注释到自动文档
  • Docker 部署 Elasticsearch 8.12 + Kibana + Nginx 负载均衡
  • yolo 训练 动态改变类别
  • SQL大表关联优化全攻略
  • 第五章:构建用户界面(UMG) - 游戏内HUD
  • CSS 雪碧图和 SVG 雪碧图的原理和区别
  • 网站底部代码特效邢台网红隧道
  • 网站降权表现营销型公司网站有哪些
  • 评估指标+数据不匹配+贝叶斯最优误差(分析方差和偏差)+迁移学习+多任务学习+端到端深度学习
  • 外国食品优秀设计网站做网站电话销售
  • 构建下一代法律智能助手:需求分析、资源整合与系统设计
  • Oracle AWR案例分析:精准定位SQL执行计划切换的时间点
  • 2025年7月一区SCI优化算法-Logistic-Gauss Circle optimizer-附Matlab免费代码
  • abpVnext 获取token报错,配置文件从sqlerver切换到oracle,再切换回来sqlerver无法获取token
  • 成都哪里有做网站的公司wordpress在本地搭建
  • C++进阶:(四)set系列容器的全面指南
  • 【Java零碎知识点】----- java.util.Random 与 Math.random()
  • 补充内容:YOLOv5损失函数解析+代码阅读
  • 北仑网站建设培训学校游戏开发需要什么学历
  • 高端装备制造提速,紧固件标准化与智能化升级成为行业新焦点
  • 6项提高电机制造质量的电气测试方案
  • 09_FastMCP 2.x 中文文档之FastMCP高级功能服务器组成详解
  • 工业之“眼”的进化:基于MEMS扫描的主动式3D视觉如何驱动柔性制造