1. 概念
在进行单元测试时,特别是针对使用了Spring框架的应用程序,我们通常需要与Spring容器交互以获取被测试对象及其依赖。传统做法是在每个测试方法中手动创建Spring容器并从中获取所需的Bean。以下面的两行常见代码为例:
ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");
Xxxx xxx = context.getBean(Xxxx.class);
这段代码做了两件事:
- 创建Spring容器:使用
ClassPathXmlApplicationContext
类的构造函数创建一个Spring容器实例。这个构造函数接受一个字符串参数,即Spring配置文件(通常是XML格式)的路径。在这个例子中,配置文件名为 "xxx.xml",位于类路径(classpath)下。ClassPathXmlApplicationContext
是Spring众多容器实现之一,它能从类路径加载XML配置文件,并据此创建和管理Bean。 - 从容器中获取Bean:调用容器实例(
context
)的getBean
方法,传入想要获取的Bean的类型或名称(这里是Xxxx.class
)。getBean
方法会查找并返回与指定类型或名称匹配的Bean实例。这样,我们就得到了需要测试的Xxxx
类的实例。
然而,每次编写单元测试时都重复这两行代码不仅显得冗余,还增加了维护成本。尤其是在大型项目中,可能会有多个测试类需要与Spring容器交互,重复的容器创建和Bean获取逻辑会使测试代码变得复杂且不易管理。
为了解决这个问题,Spring提供了与JUnit的集成方案,允许我们在不手动创建容器的情况下,让测试框架自动处理Spring容器的创建和Bean的注入。具体而言,Spring提供了一个运行器(Runner),如 SpringJUnit4ClassRunner
或更新的 SpringExtension
(对于JUnit 5),这些运行器可以配合特定的注解(如 @ContextConfiguration
)来指示测试框架使用哪个配置文件(或注解配置类)来初始化Spring容器。
以下是使用Spring整合JUnit后的简化测试代码示例:
@RunWith(SpringJUnit4ClassRunner.class) // 或 @ExtendWith(SpringExtension.class) for JUnit 5
@ContextConfiguration(locations = {"classpath:xxx.xml"})
public class XxxxTest {
@Autowired
private Xxxx xxx; // 直接注入待测试的Bean
@Test
public void testSomeMethod() {
// 在这里直接使用注入的xxx对象进行测试,无需手动创建容器和获取Bean
// ...
}
}
通过这种方式:
- 使用
@RunWith
或@ExtendWith
注解指定Spring提供的运行器,告知JUnit使用Spring的方式来运行测试。 - 使用
@ContextConfiguration
注解指定了Spring配置文件的位置(同样可以是注解配置类),现在无需在测试代码中显式创建ApplicationContext
。 - 利用
@Autowired
注解直接在测试类的字段上声明需要注入的Bean,Spring会在容器初始化后自动将对应的Bean注入到该字段。
如此一来,可以简单理解为:
以前,每次编写单元测试时,都需要手动创建Spring容器并从中获取被测试对象。这既繁琐又容易出错。
现在,通过集成Spring与JUnit,我们可以利用Spring提供的运行器和注解来自动化容器创建和Bean注入过程。测试类只需关注具体的测试逻辑,无需关心容器管理细节,代码更加简洁、易于维护。
2. JUnit4 & JUnit5
JUnit4 和 JUnit5 都是帮助Java程序员写单元测试的工具。简单来说:
JUnit4 是一款很老但很经典的单元测试框架。它使用注解(比如 @Test
、@Before
、@After
)来标记测试方法和设置测试前后的操作。你写好测试代码,然后JUnit4帮你运行这些测试,告诉你哪些通过了,哪些失败了。它还有断言方法(如 assertEquals
),让你检查程序的实际输出是否符合预期。
JUnit5 是JUnit家族的最新版本,比JUnit4更新、更强大。它继承了JUnit4的好东西(如注解),但改进了很多地方,让写测试变得更方便、更灵活:
- 新特性:JUnit5添加了更多有用的注解(如
@BeforeEach
、@AfterEach
、@DisplayName
),让测试代码更易读、更结构化。还支持参数化测试(一个测试方法跑多种输入情况),条件执行测试,以及嵌套测试(测试里面套测试)。 - 更好兼容:JUnit5全面支持现代Java版本(如Java 8及以上),能用到Lambda表达式、Stream等新特性。它还自带一个平台,不仅能跑JUnit5的测试,还能跑JUnit4甚至其他测试框架的测试。
- 更强大扩展:JUnit5提供了扩展机制(
Extension API
),让你可以更方便地定制测试行为,比如控制测试环境、模拟依赖、自定义报告等。这比JUnit4的“规则”更强大、更易于使用。
所以,如果你刚开始学习写Java单元测试,直接学JUnit5是个不错的选择,因为它是最新的、功能最全的。如果你看到一些旧代码还在用JUnit4,也不用担心,大部分基础知识是相通的,而且JUnit5也能很好地兼容运行JUnit4的测试。
3. 整合JUnit5
beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.sakurapaid.spring6.bean"/>
</beans>
3.1. 搭建子模块
创建一个名为 spring-junit
的子模块,用于存放相关的测试代码和配置。
3.2. 引入依赖
在子模块的构建文件(如pom.xml或build.gradle)中添加必要的依赖项:
<dependencies>
<!--spring context依赖-->
<!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!--spring对junit的支持相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.2</version>
</dependency>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
</dependency>
<!--log4j2的依赖-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.19.0</version>
</dependency>
</dependencies>
- Spring Context:提供Spring的核心功能,如依赖注入(DI)、AOP等,是使用Spring进行应用开发的基础。
- Spring Test:包含了Spring对单元测试和集成测试的支持,其中包含与JUnit5集成的SpringJUnit5ClassRunner等工具。
- JUnit Jupiter API:JUnit5的核心API包,包含编写单元测试所需的基本注解和断言。
- Log4j2:一个流行的日志框架,以及与SLF4J(Simple Logging Facade for Java)适配的实现,用于记录和管理测试过程中的日志信息。
3.3. 添加配置文件
创建一个名为 beans.xml
的Spring配置文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描指定包下的@Component、@Service、@Repository、@Controller等注解标记的类 -->
<context:component-scan base-package="com.sakurapaid.spring6.bean"/>
</beans>
此配置文件中,context:component-scan
标签用于自动扫描指定包(com.atguigu.spring6.bean
)下的带有Spring组件注解(如@Component
)的类,并将其注册为Spring管理的Bean。
同时,复制一个名为 log4j2.xml
的日志配置文件到项目中,用于配置Log4j2的日志记录行为。
3.4. 添加Java类
创建一个名为 User
的Java类,使用 @Component
注解标记,表示这是一个由Spring管理的Bean:
package com.sakurapaid.spring6.bean;
import org.springframework.stereotype.Component;
/**
* User类说明
* 本类用于示例Spring Bean的声明。
* @Component注解:用于标记一个类作为Spring的组件,使得该组件可以被Spring的组件扫描器识别并加入到Spring的IoC容器中。
*/
@Component
public class User {
/**
* User类的构造函数
* 该构造函数在实例化User对象时会自动执行,用于执行一些初始化操作。
*/
public User() {
System.out.println("run user");
}
}
当Spring容器初始化时,会发现并创建这个带有 @Component
注解的 User
类的实例。
3.5. 测试
编写一个JUnit5测试类 SpringJUnit5Test
,通过Spring与JUnit5的集成来测试 User
类:
package com.sakurapaid.spring6.test;
import com.sakurapaid.spring6.bean.User;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
// 方式一:使用@ExtendWith和@ContextConfiguration注解
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
// 方式二:使用@SpringJUnitConfig注解(推荐)
//@SpringJUnitConfig(locations = "classpath:bean.xml")
public class SpringJUnit5Test {
@Autowired
private User user;
@Test
public void testUser(){
System.out.println(user);
}
}
测试类中:
@SpringJUnitConfig
或@ExtendWith(SpringExtension.class) + @ContextConfiguration
:这两种方式都是用来集成Spring与JUnit5的。前者是更简洁的组合注解,后者则是分别使用注解来指定Spring测试扩展和配置文件位置。它们的作用都是让Spring在测试运行前加载配置文件,创建Spring容器,并根据容器来管理测试类中的Bean。@Autowired
:用于自动注入User
类的实例。由于User
类已被Spring管理,所以在测试类中可以通过@Autowired
注解来直接注入其实例,无需手动创建。@Test
:JUnit5的测试方法注解,表示该方法是一个单元测试。
测试方法 testUser()
中,打印出注入的 user
实例。当运行此测试时,Spring容器会先被创建,User
类的实例会被自动注入到测试类中,然后执行测试方法,打印出 User
实例的信息。
总结:这个示例展示了如何使用JUnit5编写单元测试,并通过Spring的配置文件和注解来管理测试所需的Bean。测试类借助Spring与JUnit5的集成,能够便捷地访问被测试对象及其依赖,从而专注于测试逻辑本身。同时,还配置了Log4j2用于记录测试过程中的日志信息。
方式一:使用 @ExtendWith
和 @ContextConfiguration
注解
这是JUnit5早期集成Spring的一种方式,通过两个单独的注解来完成:
@ExtendWith(SpringExtension.class)
:
-
- 这是一个JUnit5的通用扩展机制注解,用于指定一个或多个测试扩展(Extension),这些扩展能够参与到测试的生命周期管理中,添加额外的功能。
SpringExtension
是Spring专门为JUnit5提供的扩展实现,它负责在测试运行前启动Spring容器,将Spring的依赖注入和AOP等功能与JUnit5测试框架结合起来。- 使用
@ExtendWith(SpringExtension.class)
注解,意味着在运行此测试类时,JUnit5会调用SpringExtension
来处理与Spring相关的部分。
@ContextConfiguration("classpath:beans.xml")
:
-
- 这是Spring Test提供的注解,用于指定Spring容器的配置来源。在这里,它指定了一个类路径下的XML配置文件
beans.xml
。 - 当
SpringExtension
在运行测试时启动Spring容器,它会根据@ContextConfiguration
注解提供的信息加载配置文件,创建并初始化Spring容器。 - 有了这个配置,Spring容器就知道如何创建、装配和管理测试所需的Bean,包括那些被
@Autowired
注解的字段。
- 这是Spring Test提供的注解,用于指定Spring容器的配置来源。在这里,它指定了一个类路径下的XML配置文件
方式二:使用 @SpringJUnitConfig
注解(推荐)
@SpringJUnitConfig
是Spring Test为JUnit5专门提供的一个组合注解,它整合了上述两种方式的功能,使得集成Spring更加简洁:
@SpringJUnitConfig(locations = "classpath:beans.xml")
-
- 这是一个单一注解,它同时包含了
@ExtendWith(SpringExtension.class)
和@ContextConfiguration("classpath:beans.xml")
的功能。 - 使用
@SpringJUnitConfig
,您只需要指定Spring配置文件的位置(如"classpath:beans.xml"
),而不必分别使用@ExtendWith
和@ContextConfiguration
。 - 这样做的好处是代码更简洁,易于阅读和理解,同时也避免了重复指定Spring扩展和配置源。
- 这是一个单一注解,它同时包含了
- 方式一 是通过分别使用
@ExtendWith(SpringExtension.class)
和@ContextConfiguration("classpath:beans.xml")
注解来集成Spring与JUnit5。 - 方式二 则是使用更简洁的
@SpringJUnitConfig(locations = "classpath:beans.xml")
注解,它合并了前两种注解的功能,是更推荐的集成方式。两者的目的都是启动Spring容器,加载指定的配置,以便在JUnit5测试中利用Spring的依赖注入和管理功能。对于初学者来说,直接使用@SpringJUnitConfig
会更加直观和简便。
4. 整合JUnit4
JUnit4在公司也会经常用到,在此也学习一下
4.1. 添加依赖
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
4.2. 测试
编写一个JUnit4测试类 SpringJUnit4Test,通过Spring与JUnit4的集成来测试 User 类
import com.atguigu.spring6.bean.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:beans.xml")
public class SpringJUnit4Test {
@Autowired
private User user;
@Test
public void testUser(){
System.out.println(user);
}
}
测试类中:
- @RunWith(SpringJUnit4ClassRunner.class):
-
- 这是一个JUnit4特有的注解,用于指定测试类的运行器(Runner)。SpringJUnit4ClassRunner 是Spring为JUnit4提供的特殊运行器,它能在运行测试之前启动Spring容器,将Spring的依赖注入等功能与JUnit4测试框架结合。
- 使用 @RunWith(SpringJUnit4ClassRunner.class),意味着在运行此测试类时,JUnit4会使用 SpringJUnit4ClassRunner 而不是默认的 JUnit4ClassRunner,从而启用Spring容器支持。
- @ContextConfiguration("classpath:beans.xml"):
-
- 这同样是Spring Test提供的注解,用于指定Spring容器的配置来源。在这里,它指定了一个类路径下的XML配置文件 beans.xml。
- 当 SpringJUnit4ClassRunner 启动Spring容器时,它会根据 @ContextConfiguration 注解提供的信息加载配置文件,创建并初始化Spring容器。
- 有了这个配置,Spring容器就知道如何创建、装配和管理测试所需的Bean,包括那些被 @Autowired 注解的字段。
- @Autowired:
-
- 用于自动注入 User 类的实例。由于 User 类已被Spring管理,所以在测试类中可以通过 @Autowired 注解来直接注入其实例,无需手动创建。
- @Test:
-
- JUnit4的测试方法注解,表示该方法是一个单元测试。
测试方法 testUser() 中,打印出注入的 user 实例。当运行此测试时,Spring容器会先被创建,User 类的实例会被自动注入到测试类中,然后执行测试方法,打印出 User 实例的信息。
总结:这个示例展示了如何使用JUnit4编写单元测试,并通过Spring的配置文件和注解来管理测试所需的Bean。测试类借助Spring与JUnit4的集成,能够便捷地访问被测试对象及其依赖,从而专注于测试逻辑本身。与JUnit5相比,JUnit4的集成方式使用了不同的注解(如 @RunWith),但基本思路是一致的:启动Spring容器,加载配置,注入依赖,然后执行测试。