目前在写单元测试的时候,在测试类中依赖到了需要spring管理的bean,但是没有使用spring的测试支持,导致无法自动注入这些依赖,下面通过这个机会了解一下@SpringBootTest这个注解的原理。
规范的测试程序:
java
@SpringBootTest
public class ApiTest {
private final XXXMapper mapper;
@Autowired
public ApiTest(XXXMapper mapper) {
this.mappermapper = mapper;
}
@Test
public void deleteDupliteTranslationsTest( ) {
mapper.xxx();
}
}
仅使用junit,运行测试程序的时候会报错:
java
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter
需要引入的依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>testtest</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
一. 单元测试
以下是关于Java中单元测试的详细介绍:
单元测试的概念
单元测试是软件开发中的一种测试方法,旨在对软件中的**最小可测试单元(如方法、类等)进行验证**。在Java中,通常是**针对一个类的方法进行测试**,检查方法的输入与输出是否符合预期,以确保代码的正确性和可靠性。
单元测试的重要性
- 保证代码质量:通过对每个单元进行独立测试,能在开发早期发现代码中的缺陷和错误,避免问题在后续集成和系统测试阶段扩大,降低修复成本。
- 提高代码可维护性:编写良好的单元测试可以使代码结构更加清晰,模块之间的耦合度更低。当需要对代码进行修改或扩展时,单元测试可以帮助开发者快速验证修改是否对其他部分产生影响。
- 支持重构:有了完善的单元测试,开发者在对代码进行重构时可以更加放心,因为可以通过运行单元测试来确保重构后的代码功能仍然正确。
- 促进代码设计:在编写单元测试的过程中,开发者需要考虑代码的可测试性,这会促使他们设计出更加易于测试、解耦和模块化的代码。
常用的单元测试框架
- JUnit :是Java中最流行的单元测试框架之一。它提供了一组注解和断言方法,用于编写和运行单元测试。例如,
@Test
注解用于标记测试方法,assertEquals
方法用于验证方法的返回值是否与预期值相等。以下是一个简单的JUnit测试示例:
java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(5, 3);
assertEquals(8, result);
}
}
class Calculator {
public int add(int num1, int num2) {
return num1 + num2;
}
}
- TestNG:与JUnit类似,但功能更加强大,支持更多的测试功能,如参数化测试、数据驱动测试、分组测试等。它使用注解来定义测试方法和测试套件。以下是一个TestNG的参数化测试示例:
java
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class CalculatorTest {
@Test(dataProvider = "addData")
public void testAdd(int num1, int num2, int expected) {
Calculator calculator = new Calculator();
int result = calculator.add(num1, num2);
assertEquals(result, expected);
}
@DataProvider(name = "addData")
public Object[][] addData() {
return new Object[][]{
{5, 3, 8},
{10, 20, 30},
{-5, 5, 0}
};
}
}
class Calculator {
public int add(int num1, int num2) {
return num1 + num2;
}
}
单元测试的最佳实践
- 保持测试的独立性:每个单元测试应该独立于其他测试,不依赖于其他测试的执行顺序或状态。
- 编写清晰的测试方法名 :测试方法名应该能够清晰地表达测试的目的,例如
testAddPositiveNumbers
、testDivideByZero
等。 - 全面覆盖各种情况:尽可能覆盖各种边界情况、异常情况和正常情况,确保代码在各种输入下都能正确运行。
- 及时更新测试:当代码发生变化时,及时更新相应的单元测试,确保测试的有效性。
- 避免测试中的副作用:测试方法不应该对系统的其他部分产生持久的副作用,例如修改数据库记录、文件等。
二.@SpringBootTest
注解的原理
1. 启动 Spring 上下文
- 触发机制 :
- 当你在测试类上添加
@SpringBootTest
注解时,JUnit 5 的<font style="color:#DF2A3F;">SpringExtension</font>
会拦截测试类的执行过程。SpringExtension
是 JUnit 5 提供的一个扩展,专门用于集成 Spring 框架。 - 在测试开始前,
SpringExtension
会**启动一个 Spring 应用程序上下文**。这个上下文类似于你在运行 Spring Boot 应用程序时启动的上下文,会根据你的配置(例如application.properties
或application.yml
)、配置类(使用@Configuration
注解的类)以及 Spring Boot 的自动配置机制来创建和管理 Bean。
- 当你在测试类上添加
2. 依赖注入
- 自动扫描和 Bean 管理 :
- Spring 会扫描你的项目,根据
@Component
、@Service
、@Repository
、@Controller
等**注解创建相应的 Bean,并将它们存储在应用程序上下文中**。这些 Bean 是 Spring 管理的对象实例,它们的生命周期由 Spring 控制,包括依赖注入、初始化、销毁等。 - 对于测试类中使用
@Autowired
注解的属性或构造函数参数,Spring 会自动从上下文中查找匹配的 Bean,并将其注入到相应的位置。 - 在你的测试类中,使用
@Autowired
注入 和xxxMapper
时,Spring 会在启动的上下文中查找相应的 Bean 实例,并将它们注入到测试类的构造函数中。这是因为这些 Mapper 通常会在你的 Spring 应用中被标记为@Mapper
或@Repository
等注解,从而被 Spring 识别和管理。
- Spring 会扫描你的项目,根据
3. 测试执行
- 上下文共享和重用 :
- Spring 会根据测试类的需求,决定是否创建新的上下文或重用现有的上下文。在某些情况下,多个测试类可能共享同一个上下文,以提高性能。例如,如果多个测试类使用相同的配置和依赖,Spring 会尝试复用已经创建的上下文,避免重复创建相同的 Bean,从而加快测试执行速度。
- 当执行测试方法时,由于所需的依赖已经通过
@Autowired
注入到测试类中,测试方法可以使用这些依赖进行测试。
4. 与 Spring Boot 的集成
- 配置和自动配置 :
- Spring Boot 的自动配置机制会根据项目的依赖和配置自动配置一些 Bean。例如,如果你添加了
spring-boot-starter-data-jpa
依赖,Spring Boot 会自动配置 JPA 相关的 Bean,如EntityManagerFactory
、JpaTransactionManager
等。 @SpringBootTest
注解会利用 Spring Boot 的这些自动配置功能,确保测试环境与实际应用环境尽可能相似,包括加载配置文件、启用配置类等。这样可以保证测试的准确性和可靠性,因为测试是在与实际运行环境相似的条件下进行的。
- Spring Boot 的自动配置机制会根据项目的依赖和配置自动配置一些 Bean。例如,如果你添加了
总结
@SpringBootTest
注解的核心原理是利用** Spring 框架和 Spring Boot 的自动配置机制,在测试类执行前启动一个 Spring 应用程序上下文,然后根据**<font style="color:#DF2A3F;">@Autowired</font>**
等注解将上下文中的 Bean 注入到测试类中。**- 这样做可以让你的测试代码方便地使用 Spring 管理的资源和服务,确保测试环境与实际应用环境的一致性,同时充分利用 Spring 的依赖注入功能,使测试更加方便和可靠。
- 此外,你可以通过该注解的各种属性来进一步定制测试环境,以满足不同的测试需求,如测试不同的配置场景、不同的 Web 环境等。
三.Spring 应用程序上下文
以下是关于 Spring 应用程序上下文的详细解释:
1. 容器概念
- 定义 :
- Spring 应用程序上下文(ApplicationContext)是 Spring 框架的核心容器,它负责管理对象(Bean)的创建、配置和生命周期。可以将其看作是一个高级容器,比 BeanFactory 更强大和灵活,它不仅包含了 BeanFactory 的功能,还提供了更多的企业级特性,如**事件发布、国际化支持、资源加载、消息解析等。**
2. Bean 的存储和管理
- 存储 :
- 应用程序上下文存储了 Spring 管理的 Bean 实例。当你使用 Spring 开发应用程序时,会在类上添加诸如
<font style="color:#DF2A3F;">@Component</font>
、<font style="color:#DF2A3F;">@Service</font>
、<font style="color:#DF2A3F;">@Repository</font>
、<font style="color:#DF2A3F;">@Controller</font>
等注解,这些类会被 Spring 识别为 Bean 候选者。Spring 应用程序上下文会将这些类实例化为 Bean 并存储起来。 - 例如,你有一个
UserService
类被标注为@Service
:
- 应用程序上下文存储了 Spring 管理的 Bean 实例。当你使用 Spring 开发应用程序时,会在类上添加诸如
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 服务的方法
}
plain
- 当 Spring 应用程序上下文启动时,它会创建一个 `UserService` 的实例,并将其存储在容器中,这个实例就是一个 Bean。
3. Bean 的创建和配置
- 创建 :
- 应用程序上下文会根据配置信息创建 Bean。这些配置信息可以来自多个方面,包括:
- 组件扫描 :使用
@ComponentScan
注解,Spring 会扫描指定包下的类,对于标注了@Component
及其衍生注解(如@Service
、@Repository
、@Controller
)的类,会创建相应的 Bean。 - Java 配置类 :使用
@Configuration
注解的类,其中通过@Bean
注解的方法可以创建 Bean。例如:
- 组件扫描 :使用
- 应用程序上下文会根据配置信息创建 Bean。这些配置信息可以来自多个方面,包括:
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
plain
- **XML 配置**:在 XML 文件中配置 Bean,虽然这种方式在现代 Spring 开发中使用较少,但在一些遗留系统中可能存在:
xml
<beans>
<bean id="userService" class="com.example.UserService"/>
</beans>
plain
- **配置元数据的合并**:应用程序上下文会综合考虑上述各种配置信息,最终决定哪些类需要创建为 Bean 以及如何创建它们。
4. Bean 的生命周期管理
- 初始化 :
- 当 Bean 被创建后,Spring 可以调用其初始化方法,这可以通过以下几种方式实现:
- 在 Bean 的类中实现
<font style="color:#DF2A3F;">InitializingBean</font>
接口并实现**<font style="color:#DF2A3F;">afterPropertiesSet</font>**<font style="color:#DF2A3F;">()</font>
方法; - 使用
@PostConstruct
注解标注一个方法; - 在 XML 配置中指定
init-method
。
- 在 Bean 的类中实现
- 当 Bean 被创建后,Spring 可以调用其初始化方法,这可以通过以下几种方式实现:
- 依赖注入 :
- 应用程序上下文会自动将一个 Bean 所依赖的其他 Bean 注入到该 Bean 中,这可以通过构造函数注入、属性注入或方法注入实现。例如,假设
UserService
依赖UserRepository
:
- 应用程序上下文会自动将一个 Bean 所依赖的其他 Bean 注入到该 Bean 中,这可以通过构造函数注入、属性注入或方法注入实现。例如,假设
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
plain
- Spring 会在创建 `UserService` 时,找到 `UserRepository` 的 Bean 并将其注入到 `UserService` 的构造函数中。
‼️ 这里的注入指的是将某个bean所依赖的bean注入到这个bean的构造函数中,这样在这个bean中就可以使用所依赖的bean,这也是为什么会出现循环依赖的问题。
- 销毁 :
- 当应用程序关闭或上下文被销毁时,Spring 可以调用 Bean 的销毁方法,这可以通过实现
DisposableBean
接口的destroy()
方法,或者使用@PreDestroy
注解,或者在 XML 中指定destroy-method
来实现。
- 当应用程序关闭或上下文被销毁时,Spring 可以调用 Bean 的销毁方法,这可以通过实现
5. 作用域
- 不同作用域 :
- Spring 应用程序上下文可以管理 Bean 的作用域,常见的作用域有:
- Singleton:默认作用域,在整个应用程序上下文中,一个 Bean 只有一个实例。
- Prototype:每次请求 Bean 时都会创建一个新的实例。
- Request:在一个 HTTP 请求中,一个 Bean 只有一个实例(仅适用于 Web 应用)。
- Session:在一个 HTTP 会话中,一个 Bean 只有一个实例(仅适用于 Web 应用)。
- Spring 应用程序上下文可以管理 Bean 的作用域,常见的作用域有:
6. 高级特性
- 事件发布 :
- 应用程序上下文可以作为事件发布者,你可以定义事件监听器来监听和处理事件。例如:
java
import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
// 处理事件
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class EventPublisherService {
@Autowired
private ApplicationEventPublisher publisher;
public void publishEvent() {
publisher.publishEvent(new MyEvent(this));
}
}
plain
- 这里 `EventPublisherService` 可以发布 `MyEvent`,而 `MyEventListener` 会监听并处理 `MyEvent`。
- 资源加载:
- 可以使用应用程序上下文加载各种资源,如文件、URL 等。例如:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
@Service
public class ResourceService {
@Autowired
private ApplicationContext context;
public void loadResource() throws Exception {
Resource resource = context.getResource("classpath:myfile.txt");
// 处理资源
}
}
总结
- Spring 应用程序上下文是一个强大的容器,它存储和管理 Spring 中的 Bean,包括创建、配置和管理它们的生命周期。
- 它综合了多种配置信息,如组件扫描、Java 配置和 XML 配置,以创建所需的 Bean。
- 它还提供了依赖注入、事件发布、资源加载等高级特性,使得开发人员可以更方便地开发复杂的企业级应用程序,同时也方便了应用程序的测试和维护。
应用程序上下文在测试中的使用
- 在测试中使用
@SpringBootTest
时,应用程序上下文会根据你的测试需求启动,为测试类中的@Autowired
字段注入所需的 Bean。这样可以确保测试代码可以使用 Spring 管理的服务和资源,就像在实际应用中一样,提高测试的可靠性和有效性。同时,也方便你测试 Spring 组件之间的交互和依赖,因为这些组件可以从上下文获取所需的 Bean 实例。
总之,Spring 应用程序上下文是 Spring 框架的核心,它为应用程序的开发、运行和测试提供了强大的基础架构,使开发人员能够更专注于业务逻辑而不是对象的创建和管理等细节。