Java开发经验——Spring Test 常见错误

摘要

本文详细介绍了Java开发中Spring Test的常见错误和解决方案。文章首先概述了Spring中进行单元测试的多种方法,包括使用JUnit和Spring Boot Test进行集成测试,以及Mockito进行单元测试。接着,文章分析了Spring资源文件扫描不到的问题,并提供了解决方案。最后,文章探讨了Spring的Mock问题,包括Spring Context启动缓慢的原因和优化方法。

1. Spring使用的测试

在 Spring 中,进行单元测试的方式有多种,主要取决于你希望测试的对象以及使用的测试框架。Spring 提供了丰富的测试支持来帮助开发者测试其应用中的各个组件。常见的 Spring 单元测试方法包括以下几种:

1.1. 使用 JUnit 和 Spring Boot Test((多用于集成测试,启动整个 Spring 容器)

1.1.1. @SpringBootTest

@SpringBootTest 是最常用的单元测试注解之一,它会启动 Spring 容器并加载整个 Spring 上下文,适用于集成测试。通常用于测试一个较大的功能,涉及多个组件和服务。

@SpringBootTest
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    void testServiceMethod() {
        assertNotNull(myService);
        assertEquals("expected result", myService.someMethod());
    }
}
  • 优点: 自动加载整个 Spring 应用上下文,能够进行集成测试。
  • 适用场景: 测试需要 Spring 配置、服务和其他组件的复杂业务逻辑。

1.1.2. @WebMvcTest

@WebMvcTest 主要用于测试 Spring MVC 控制器。它只会启动 Web 层相关的组件,不会启动整个 Spring 上下文,因此启动速度较快。

@WebMvcTest(MyController.class)
public class MyControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testController() throws Exception {
        mockMvc.perform(get("/api/endpoint"))
                .andExpect(status().isOk())
                .andExpect(content().string("Expected response"));
    }
}
  • 优点: 只加载 Web 层相关的配置,启动速度快,适合单元测试。
  • 适用场景: 测试控制器和 Web 层的请求响应。

1.1.3. @DataJpaTest

@DataJpaTest 用于测试与数据库相关的功能。它只会启动与 JPA 相关的配置,并且自动配置一个嵌入式数据库,适合用于测试数据访问层。

@DataJpaTest
public class MyRepositoryTest {

    @Autowired
    private MyRepository myRepository;

    @Test
    void testFindById() {
        Optional<MyEntity> entity = myRepository.findById(1L);
        assertTrue(entity.isPresent());
    }
}
  • 优点: 快速配置并测试数据库访问,适合单元测试 Repository 层。
  • 适用场景: 测试与数据库交互的功能,如 Repository 类。

1.1.4. @MockBean

@SpringBootTest
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @MockBean
    private MyRepository myRepository;

    @Test
    void testServiceMethod() {
        when(myRepository.findById(1L)).thenReturn(Optional.of(new MyEntity()));

        MyEntity entity = myService.getEntity(1L);
        assertNotNull(entity);
    }
}
  • 优点: 可以模拟依赖的 Bean,避免在单元测试时连接到真实的数据库或其他外部服务。
  • 适用场景: 测试服务层逻辑时,不依赖实际的数据库或外部服务。

1.2. 使用 Mockito 进行单元测试

Mockito 是常用的 Java 测试框架,可以用来模拟对象(Mock)和验证方法调用。它可以与 Spring 集成,用于服务层或控制器层的单元测试。

1.2.1. @Mock@InjectMocks

@Mock 用于创建一个模拟对象,@InjectMocks 会自动将模拟对象注入到被测试的类中。结合 JUnit 使用时,可以对类中的依赖进行模拟,确保只测试该类的逻辑。

java


复制代码
@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {

    @Mock
    private MyRepository myRepository;

    @InjectMocks
    private MyService myService;

    @Test
    public void testServiceMethod() {
        when(myRepository.findById(1L)).thenReturn(Optional.of(new MyEntity()));

        MyEntity entity = myService.getEntity(1L);
        assertNotNull(entity);
    }
}
  • 优点: 只测试服务方法,依赖项完全由 Mockito 模拟。
  • 适用场景: 单元测试业务逻辑,模拟数据访问层或外部服务。

1.2.2. @MockBean 与 Spring 配合

在 Spring 环境下使用 Mockito,可以通过 @MockBean 注解将模拟对象注入到 Spring 应用上下文中。这样,你可以测试服务层或控制器层,模拟外部依赖。

java


复制代码
@SpringBootTest
public class MyServiceTest {

    @MockBean
    private MyRepository myRepository;

    @Autowired
    private MyService myService;

    @Test
    void testServiceMethod() {
        when(myRepository.findById(1L)).thenReturn(Optional.of(new MyEntity()));

        MyEntity entity = myService.getEntity(1L);
        assertNotNull(entity);
    }
}
  • 优点: 模拟 Spring 管理的 Bean,可以通过 Spring 容器注入,避免直接依赖外部组件。
  • 适用场景: 测试 Spring 容器管理的组件,模拟其依赖。

1.2.3. 使用 @TestConfiguration 创建自定义配置

@TestConfiguration 允许你为测试创建一个特殊的配置类,可以在测试中替换部分 Bean 配置。

@TestConfiguration
public class MyTestConfig {

    @Bean
    public MyService myService() {
        return new MyService(new MyRepositoryMock());
    }
}
  • 优点: 在测试中使用自定义配置,替代生产环境中的配置。
  • 适用场景: 在单元测试中需要使用特定的测试配置或模拟 Bean 时。

1.2.4. JUnit 5 注解测试@BeforeEach@AfterEach

这些是 JUnit 5 的生命周期注解,用于在每个测试方法之前和之后执行特定的代码。常用于初始化和清理测试环境。

@BeforeEach
void setUp() {
    // 初始化代码
}

@AfterEach
void tearDown() {
    // 清理代码
}
  • 优点: 每个测试方法执行之前和之后执行特定的初始化和清理逻辑。
  • 适用场景: 初始化和清理测试环境

1.2.5. @TestInstance 控制生命周期

@TestInstance 是 JUnit 5 中的注解,用于控制测试类实例化的生命周期。它可以设置为 PER_CLASS,表示测试类只实例化一次,而不是每个测试方法实例化一次。

java


复制代码
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyServiceTest {
    // 测试方法
}
  • 优点: 可以避免每个测试方法都实例化测试类,适合需要在类级别共享状态的测试。
  • 适用场景: 需要类级别共享状态或资源的场景。

Spring 提供了多种单元测试方法,适用于不同层次的测试需求。常用的方法包括:

  1. @SpringBootTest:用于集成测试,启动整个 Spring 容器。
  2. @WebMvcTest:用于测试 Spring MVC 控制器。
  3. @DataJpaTest:用于测试 JPA 数据访问层。
  4. @MockBean:模拟依赖 Bean,适合服务层测试。
  5. Mockito:用于模拟依赖和验证方法调用,适合单元测试。

根据需要的测试粒度选择合适的测试方法,可以确保高效且全面的测试。

2. SpringBootTest实现单元测试

2.1. SpringBootTest项目与源码示例

package com.zhuangxiaoyan.unit;

import org.springframework.stereotype.Service;

/**
 * CalculatorService
 *
 * @author xjl
 * @version 2024/11/24 10:29
 **/
@Service
public class CalculatorService {
    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }
}
package com.zhuangxiaoyan.unit;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * CalculatorServiceTest
 *
 * @author xjl
 * @version 2024/11/24 10:29
 **/
public class CalculatorServiceTest {
    private final CalculatorService calculatorService = new CalculatorService();

    @Test
    void testAdd() {
        int result = calculatorService.add(2, 3);
        assertEquals(5, result);
    }

    @Test
    void testSubtract() {
        int result = calculatorService.subtract(5, 3);
        assertEquals(2, result);
    }
}
package com.zhuangxiaoyan.unit;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
class UnitApplicationTests {


    @Autowired
    private CalculatorService calculatorService;

    @Test
    void contextLoads() {
    }

     @Test
    void testAdd() {
        int result = calculatorService.add(4, 6);
         System.out.println(result);
        assertEquals(10, result);
    }

    @Test
    void testSubtract() {
        int result = calculatorService.subtract(9, 4);
        System.out.println(result);
        assertEquals(5, result);
    }

}

2.2. org.junit.jupiter.api.Test;和JUNIT5 的区别是什么

org.junit.jupiter.api.Test 是 JUnit 5 中的一个注解,而 JUnit 5 是 JUnit 框架的最新版本。

2.2.1. JUnit 4 vs. JUnit 5 的区别

JUnit 4:

  • 使用 @Test 注解,通常在 org.junit 包下。
  • 没有 @BeforeEach@AfterEach,而是使用 @Before@After 注解。
  • 扩展性较差,不像 JUnit 5 那样有完整的扩展机制。

JUnit 5:

  • 使用 @Test 注解,位于 org.junit.jupiter.api.Test 包下。
  • 引入了新的注解,如 @BeforeEach@AfterEach(替代 @Before@After)。
  • 引入了新的功能,如参数化测试、条件测试、测试生命周期钩子等。
  • 提供了更强大的扩展机制,允许用户编写自己的扩展(例如 @ExtendWith)。

2.2.2. JUnit 5 的新特性

  • 生命周期钩子
    • @BeforeEach 替代了 @Before
    • @AfterEach 替代了 @After
    • @BeforeAll@AfterAll 用于静态方法,替代了 JUnit 4 的 @BeforeClass@AfterClass
  • 扩展性和条件化测试
    • JUnit 5 引入了扩展机制,通过 @ExtendWith 可以将自定义的扩展类添加到测试类中。
    • 可以通过 @EnabledIf@DisabledIf 条件注解来有条件地启用或禁用测试。
  • 参数化测试
    • JUnit 5 提供了更强大的参数化测试支持,如 @ValueSource@EnumSource@MethodSource 等。
  • 更好的报告和兼容性
    • 更好的报告功能,能够输出更详细的测试结果。
    • 通过 JUnit Vintage 模块,JUnit 5 可以与 JUnit 3 和 JUnit 4 的测试兼容运行。

2.3. Mockito 和 JUnit 版本兼容问题

出现了 Could not initialize plugin: interface org.mockito.plugins.MockMaker 这个错误?

Mockito 插件错误通常是由于 Mockito 版本JUnit 版本 不兼容,或者你的项目中的依赖版本不一致。

解决办法:确保你使用的是兼容的 MockitoJUnit 版本。如果你正在使用 JUnit 5,则需要使用兼容的 Mockito 版本。

Maven 依赖示例:如果你使用 JUnit 5 和 Mockito,你应该确保你的 pom.xml 中有以下依赖:

<dependencies>
    <!-- JUnit 5 依赖 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.8.2</version> <!-- 根据需要选择版本 -->
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.8.2</version> <!-- 根据需要选择版本 -->
        <scope>test</scope>
    </dependency>

    <!-- Mockito 依赖 -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>4.0.0</version> <!-- 根据需要选择版本 -->
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>4.0.0</version> <!-- 确保使用与 mockito-core 兼容的版本 -->
        <scope>test</scope>
    </dependency>
</dependencies>

确保使用与 Mockito 4.xJUnit 5 兼容的版本(如上所示)。如果你使用的是 JUnit 4,那么需要使用与之兼容的 Mockito 版本

3. Spring资源文件扫描不到

@RestController
public class HelloController {

    @Autowired
    HelloWorldService helloWorldService;

    @RequestMapping(path = "hi", method = RequestMethod.GET)
    public String hi() throws Exception{
        return  helloWorldService.toString() ;
    };

}

当访问 http://localhost:8080/hi 时,上述接口会打印自动注入的HelloWorldService类型的 Bean。而对于这个 Bean 的定义,我们这里使用配置文件的方式进行。

  1. 定义 HelloWorldService,具体到 HelloWorldService 的实现并非本讲的重点,所以我们可以简单实现如下:
public class HelloWorldService {
}
  1. 定义一个 spring.xml,在这个 XML 中定义 HelloWorldServic 的Bean,并把这个 spring.xml 文件放置在/src/main/resources 中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="helloWorldService" class="com.spring.puzzle.others.test.example1.HelloWorldService">
  </bean>
</beans>
  1. 定义一个 Configuration 引入上述定义 XML,具体实现方式如下:
@Configuration
@ImportResource(locations = {"spring.xml"})
public class Config {
}

完成上述步骤后,我们就可以使用 main() 启动起来。测试这个接口,一切符合预期。那么接下来,我们来写一个测试:

@SpringBootTest()
class ApplicationTests {

    @Autowired
    public HelloController helloController;

    @Test
    public void testController() throws Exception {
        String response = helloController.hi();
        Assert.notNull(response, "not null");
    }

}

当我们运行上述测试的时候,会发现测试失败了,报错如下:

3.1. 问题解析

启动程序加载spring.xml

首先看下调用栈:

可以看出,它最终以 ClassPathResource 形式来加载,这个资源的情况如下:

而具体到加载实现,它使用的是 ClassPathResource#getInputStream 来加载spring.xml文件:

从上述调用及代码实现,可以看出最终是可以加载成功的。

测试加载spring.xml

首先看下调用栈:

可以看出它是按 ServletContextResource 来加载的,这个资源的情况如下:

具体到实现,它最终使用的是 MockServletContext#getResourceAsStream 来加载文件:

@Nullable
public InputStream getResourceAsStream(String path) {
    String resourceLocation = this.getResourceLocation(path);
    Resource resource = null;

    try {
        resource = this.resourceLoader.getResource(resourceLocation);
        return !resource.exists() ? null : resource.getInputStream();
    } catch (IOException | InvalidPathException var5) {
        if (this.logger.isWarnEnabled()) {
            this.logger.warn("Could not open InputStream for resource " + (resource != null ? resource : resourceLocation), var5);
        }

        return null;
    }
}

你可以继续跟踪它的加载位置相关代码,即 getResourceLocation():

protected String getResourceLocation(String path) {
    if (!path.startsWith("/")) {
        path = "/" + path;
    }
    //加上前缀:/src/main/resources
    String resourceLocation = this.getResourceBasePathLocation(path);
    if (this.exists(resourceLocation)) {
        return resourceLocation;
    } else {
        //{"classpath:META-INF/resources", "classpath:resources", "classpath:static", "classpath:public"};
        String[] var3 = SPRING_BOOT_RESOURCE_LOCATIONS;
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String prefix = var3[var5];
            resourceLocation = prefix + path;
            if (this.exists(resourceLocation)) {
                return resourceLocation;
            }
        }

        return super.getResourceLocation(path);
    }
}

你会发现,它尝试从下面的一些位置进行加载:

classpath:META-INF/resources
classpath:resources
classpath:static
classpath:public
src/main/webapp

如果你仔细看这些目录,你还会发现,这些目录都没有spring.xml。或许你认为源文件src/main/resource下面不是有一个 spring.xml 么?那上述位置中的classpath:resources不就能加载了么?

那你肯定是忽略了一点:当程序运行起来后,src/main/resource 下的文件最终是不带什么resource的。关于这点,你可以直接查看编译后的目录(本地编译后是 target\classes 目录),示例如下:

所以,最终我们在所有的目录中都找不到spring.xml,并且会报错提示加载不了文件。报错的地方位于 ServletContextResource#getInputStream 中:

@Override
public InputStream getInputStream() throws IOException {
   InputStream is = this.servletContext.getResourceAsStream(this.path);
   if (is == null) {
      throw new FileNotFoundException("Could not open " + getDescription());
   }
   return is;
}

3.2. 问题修正

从上述案例解析中,我们了解到了报错的原因,那么如何修正这个问题?这里我们可以采用两种方式。

  1. 在加载目录上放置 spring.xml

就本案例而言,加载目录有很多,所以修正方式也不少,我们可以建立一个 src/main/webapp,然后把 spring.xml 复制一份进去就可以了。也可以在/src/main/resources 下面再建立一个 resources 目录,然后放置进去也可以。

  1. 在 @ImportResource 使用classpath加载方式
@Configuration
//@ImportResource(locations = {"spring.xml"})
@ImportResource(locations = {"classpath:spring.xml"})
public class Config {
}

这里,我们可以通过 Spring 的官方文档简单了解下不同加载方式的区别,参考 Chapter 4. Resources:

很明显,我们一般都不会使用本案例的方式(即locations = {“spring.xml”},无任何“前缀”的方式),毕竟它已经依赖于使用的 ApplicationContext。而 classPath 更为普适些,而一旦你按上述方式修正后,你会发现它加载的资源已经不再是 ServletContextResource,而是和应用程序一样的 ClassPathResource,这样自然可以加载到了。

4. Spring的Mock问题

有时候,我们会发现 Spring Test 运行起来非常缓慢,寻根溯源之后,你会发现主要是因为很多测试都启动了Spring Context,示例如下:

那么为什么有的测试会多次启动 Spring Context?在具体解析这个问题之前,我们先模拟写一个案例来复现这个问题。

我们先在 Spring Boot 程序中写几个被测试类:

@Service
public class ServiceOne {
}
@Service
public class ServiceTwo {
}

然后分别写出对应的测试类:

@SpringBootTest()
class ServiceOneTests {

    @MockBean
    ServiceOne serviceOne;

    @Test
    public void test(){
        System.out.println(serviceOne);
    }
}

@SpringBootTest()
class ServiceTwoTests {
    @MockBean
    ServiceTwo serviceTwo;
    @Test
    public void test(){
        System.out.println(serviceTwo);
    }
}

在上述测试类中,我们都使用了@MockBean。写完这些程序,批量运行测试,你会发现Spring Context 果然会被运行多次。那么如何理解这个现象,是错误还是符合预期?接下来我们具体来解析下。

4.1. 案例解析

当我们运行一个测试的时候,正常情况是不会重新创建一个 Spring Context 的。这是因为 Spring Test 使用了 Context 的缓存以避免重复创建 Context。那么这个缓存是怎么维护的呢?我们可以通过DefaultCacheAwareContextLoaderDelegate#loadContext来看下 Context 的获取和缓存逻辑:

public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
    synchronized(this.contextCache) {
        ApplicationContext context = this.contextCache.get(mergedContextConfiguration);
        if (context == null) {
            try {
                context = this.loadContextInternal(mergedContextConfiguration);
                //省略非关键代码
                this.contextCache.put(mergedContextConfiguration, context);
            } catch (Exception var6) {
            //省略非关键代码
            }
        } else if (logger.isDebugEnabled()) {
            //省略非关键代码
        }

        this.contextCache.logStatistics();
        return context;
    }
}

从上述代码可以看出,缓存的 Key 是 MergedContextConfiguration。所以一个测试要不要启动一个新的 Context,就取决于根据这个测试 Class 构建的 MergedContextConfiguration 是否相同。而是否相同取决于它的 hashCode() 实现:

public int hashCode() {
    int result = Arrays.hashCode(this.locations);
    result = 31 * result + Arrays.hashCode(this.classes);
    result = 31 * result + this.contextInitializerClasses.hashCode();
    result = 31 * result + Arrays.hashCode(this.activeProfiles);
    result = 31 * result + Arrays.hashCode(this.propertySourceLocations);
    result = 31 * result + Arrays.hashCode(this.propertySourceProperties);
    result = 31 * result + this.contextCustomizers.hashCode();
    result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0);
    result = 31 * result + nullSafeClassName(this.contextLoader).hashCode();
    return result;
}

从上述方法,你可以看出只要上述元素中的任何一个不同都会导致一个 Context 会重新创建出来。关于这个缓存机制和 Key 的关键因素你可以参考 Spring 的官方文档,也有所提及,这里我直接给出了链接,你可以对照着去阅读。

点击获取:Redirecting...

现在回到本案例,为什么会创建一个新的 Context 而不是复用?根源在于两个测试的contextCustomizers这个元素的不同。如果你不信的话,你可以调试并对比下。

ServiceOneTests 的 MergedContextConfiguration 示例如下:

ServiceTwoTests 的 MergedContextConfiguration 示例如下:

很明显,MergedContextConfiguration(即 Context Cache 的 Key)的 ContextCustomizer 是不同的,所以 Context 没有共享起来。而追溯到 ContextCustomizer 的创建,我们可以具体来看下。

当我们运行一个测试(testClass)时,我们会使用 MockitoContextCustomizerFactory#createContextCustomizer 来创建一个 ContextCustomizer,代码示例如下:

class MockitoContextCustomizerFactory implements ContextCustomizerFactory {
    MockitoContextCustomizerFactory() {
    }

    public ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
        DefinitionsParser parser = new DefinitionsParser();
        parser.parse(testClass);
        return new MockitoContextCustomizer(parser.getDefinitions());
    }
}

创建的过程是由 DefinitionsParser 来解析这个测试 Class(例如案例中的 ServiceOneTests),如果这个测试 Class 中包含了 MockBean 或者 SpyBean 标记的情况,则将对应标记的情况转化为 MockDefinition,最终添加到 ContextCustomizer 中。解析的过程参考 DefinitionsParser#parse:

void parse(Class<?> source) {
    this.parseElement(source);
    ReflectionUtils.doWithFields(source, this::parseElement);
}

private void parseElement(AnnotatedElement element) {
    MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.SUPERCLASS);
//MockBean 处理    annotations.stream(MockBean.class).map(MergedAnnotation::synthesize).forEach((annotation) -> {
        this.parseMockBeanAnnotation(annotation, element);
    });
//SpyBean 处理    annotations.stream(SpyBean.class).map(MergedAnnotation::synthesize).forEach((annotation) -> {
        this.parseSpyBeanAnnotation(annotation, element);
    });
}

private void parseMockBeanAnnotation(MockBean annotation, AnnotatedElement element) {
    Set<ResolvableType> typesToMock = this.getOrDeduceTypes(element, annotation.value());
    //省略非关键代码
    Iterator var4 = typesToMock.iterator();
    while(var4.hasNext()) {
        ResolvableType typeToMock = (ResolvableType)var4.next();
        MockDefinition definition = new MockDefinition(annotation.name(), typeToMock, annotation.extraInterfaces(), annotation.answer(), annotation.serializable(), annotation.reset(), QualifierDefinition.forElement(element));
        //添加到 DefinitionsParser#definitions
        this.addDefinition(element, definition, "mock");
    }
}

那说了这么多,Spring Context 重新创建的根本原因还是在于使用了@MockBean 且不同,从而导致构建的 MergedContextConfiguration 不同,而 MergedContextConfiguration 正是作为 Cache 的 Key,Key 不同,Context 不能被复用,所以被重新创建了。这就是为什么在案例介绍部分,你会看到多次 Spring Context 的启动过程。而正因为“重启”,测试速度变缓慢了。

4.2. 问题修正

到这,你会发现其实这种缓慢的根源是使用了@MockBean 带来的一个正常现象。但是假设你非要去提速下,那么你可以尝试使用 Mockito 去手工实现类似的功能。当然你也可以尝试使用下面的方式来解决,即把相关的 MockBean 都定义到一个地方去。例如针对本案例,修正方案如下:

public class ServiceTests {
    @MockBean
    ServiceOne serviceOne;
    @MockBean
    ServiceTwo serviceTwo;

}

@SpringBootTest()
class ServiceOneTests extends ServiceTests{

    @Test
    public void test(){
        System.out.println(serviceOne);
    }

}

@SpringBootTest()
class ServiceTwoTests  extends ServiceTests{
    @Test
    public void test(){
        System.out.println(serviceTwo);
    }
}

重新运行测试,你会发现 Context 只会被创建一次,速度也有所提升了。相信,你也明白这么改能工作的原因了,现在每个测试对应的 Context 缓存 Key 已经相同了。

博文参考

《Spring常见错误》

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

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

相关文章

2024年亚太地区数学建模大赛D题-探索量子加速人工智能的前沿领域

量子计算在解决复杂问题和处理大规模数据集方面具有巨大的潜力&#xff0c;远远超过了经典计算机的能力。当与人工智能&#xff08;AI&#xff09;集成时&#xff0c;量子计算可以带来革命性的突破。它的并行处理能力能够在更短的时间内解决更复杂的问题&#xff0c;这对优化和…

基于 RBF 神经网络整定的 PID 控制

基于 RBF 神经网络整定的 PID 控制 是结合了传统 PID 控制和 RBF&#xff08;径向基函数&#xff09;神经网络的自适应控制方法。在这种方法中&#xff0c;RBF 神经网络用于自适应地调整 PID 控制器的增益&#xff08;比例增益 KpK_pKp​&#xff0c;积分增益 KiK_iKi​ 和微分…

空间注意力网络的性能优化与多维评估

在本文中&#xff0c;首先分析空间注意力网络&#xff08;Spatial Attention Neural Network&#xff09;在五个不同数据集上的训练结果。这些数据集包括Daily_and_Sports_Activities、WISDM、UCI-HAR、PAMAP2和OPPORTUNITY。通过对比这些结果&#xff0c;我们可以深入理解空间…

Linux——1_系统的延迟任务及定时任务

系统的延迟任务及定时任务 在系统中我们的维护工作大多数时在服务器行对闲置时进行 我们需要用延迟任务来解决自动进行的一次性的维护 延迟任务时一次性的&#xff0c;不会重复执行 当延迟任务产生输出后&#xff0c;这些输出会以邮件的形式发送给延迟任务发起者 在RHEL9中…

【数据结构】—— 线索二叉树

引入 我们现在提倡节约型杜会&#xff0c; 一切都应该节约为本。对待我们的程序当然也不例外&#xff0c;能不浪费的时间或空间&#xff0c;都应该考虑节省。我们再观察团下图的二叉树&#xff08;链式存储结构)&#xff0c;会发现指针域并不是都充分的利用了&#xff0c;有许…

NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案

EasyNVR是基于端-边-云一体化架构的安防监控视频融合云平台&#xff0c;具有简单轻量的部署方式与多样的功能&#xff0c;支持多种协议&#xff08;如GB28181、RTSP、Onvif、RTMP&#xff09;和设备类型&#xff08;IPC、NVR等&#xff09;&#xff0c;提供视频直播、录像、回放…

虚幻引擎---初识篇

一、学习途径 虚幻引擎官方文档&#xff1a;https://dev.epicgames.com/documentation/zh-cn/unreal-engine/unreal-engine-5-5-documentation虚幻引擎在线学习平台&#xff1a;https://dev.epicgames.com/community/unreal-engine/learning哔哩哔哩&#xff1a;https://www.b…

汽车HiL测试:利用TS-GNSS模拟器掌握硬件性能的仿真艺术

一、汽车HiL测试的概念 硬件在环&#xff08;Hardware-in-the-Loop&#xff0c;简称HiL&#xff09;仿真测试&#xff0c;是模型基于设计&#xff08;Model-Based Design&#xff0c;简称MBD&#xff09;验证流程中的一个关键环节。该步骤至关重要&#xff0c;因为它整合了实际…

C++编程库与框架实战——sqlite3数据库

一,SQLite数据库简介 SQLite是可以实现类似于关系型数据库中各种操作的事务性SQL数据库引擎。 SQLite可以为应用程序提供存储于本地的嵌入式数据库,帮助应用程序实现轻量级的数据存储。 SQLite是一个库文件,并不是单独的进程,它可以静态或动态链接到C++应用程序中,然后…

STM32F10x 定时器

使用定时器实现&#xff1a;B5 E5的开关 添加相关的.h路径文件 添加相关的.c配置文件 led.h文件 用于声明LED函数 #ifndef __LED_H //没有定义__LED_H #define __LED_H //就定义__LED_H #define LED1_ON GPIO_ResetBits(GPIOB,GPIO_Pin_5) #defi…

PyQt6+pyqtgraph折线图绘制显示

1、实现效果 2、环境&#xff1a; 确认已经安装pyqtgraph的模块&#xff0c;如果没有安装&#xff0c;使用命令安装&#xff1a; pip install pyqtgraph 3、代码实现&#xff1a; 绘制折线函数&#xff1a; import sys import random from PySide6.QtWidgets import QAppl…

Linux---ps命令

​​​​​​Linux ps 命令 | 菜鸟教程 (runoob.com) process status 用于显示进程的状态 USER: 用户名&#xff0c;运行此进程的用户名。PID: 进程ID&#xff08;Process ID&#xff09;&#xff0c;每个进程的唯一标识号%CPU: 进程当前使用的CPU百分比%MEM: 进程当前使用的…

高新技术行业中的知识管理:关键性、挑战、策略及工具应用

知识管理的关键性 在瞬息万变的信息时代&#xff0c;知识已成为高新技术行业的核心竞争要素。知识管理&#xff0c;这一旨在高效组织、整合并应用企业内外部知识资源的管理策略&#xff0c;对于推动高新技术企业的持续创新与发展至关重要。它不仅能够激发研发团队的创造力&…

IDEA 2024安装指南(含安装包以及使用说明 cannot collect jvm options 问题 四)

汉化 setting 中选择插件 完成 安装出现问题 1.可能是因为之前下载过的idea&#xff0c;找到连接中 文件&#xff0c;卸载即可。

【MyBatis】全局配置文件—mybatis.xml 创建xml模板

文章目录 模板文件配置元素typeAliasessettings 模板文件 创建模板 按照顺序打开【File】–>【settings】–>【Editor】–>【File and Code Templates】&#xff08;或直接搜索&#xff09; <?xml version"1.0" encoding"UTF-8" ?> <…

uni-app 发布媒介功能(自由选择媒介类型的内容) 设计

1.首先明确需求 我想做一个可以选择媒介的内容&#xff0c;来进行发布媒介的功能 &#xff08;媒介包含&#xff1a;图片、文本、视频&#xff09; 2.原型设计 发布-编辑界面 通过点击下方的加号&#xff0c;可以自由选择添加的媒介类型 但是因为预览中无法看到视频的效果&…

【Go】-go中的锁机制

目录 一、锁的基础知识 1. 互斥量/互斥锁 2. CAS&#xff08;compare and swap&#xff09; 3. 自旋锁 4. 读写锁 5. 乐观锁 & 悲观锁 6. 死锁 二、go中锁机制 1. Mutex-互斥锁 2. RWMutex-读写锁 2.1 RWMutex流程概览 2.2 写锁饥饿问题 2.3. golang的读写锁源…

Python 使用 Selenuim进行自动化点击入门,谷歌驱动,以百度为例

一、首先要下载谷歌驱动 1.&#xff08;打开谷歌浏览器 - 设置 - 关于谷歌&#xff0c;查看谷歌浏览器版本&#xff0c;否则不对应无法调用&#xff0c;会提示&#xff1a;selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This versio…

RCVS:A Unifed Registration and FusionFramework for Video Streams 译文

摘要:红外与可见光的跨模态配准与融合可以生成更全面的目标和场景信息表示。以前的框架主要关注于解决模态差异以及保留不同模态信息对不同静态图像对之间配准和融合任务性能的影响。然而&#xff0c;这些框架忽略了在现实世界设备上的实际部署&#xff0c;特别是在视频流的背景…

JDBC编程---Java

目录 一、数据库编程的前置 二、Java的数据库编程----JDBC 1.概念 2.JDBC编程的优点 三.导入MySQL驱动包 四、JDBC编程的实战 1.创造数据源&#xff0c;并设置数据库所在的位置&#xff0c;三条固定写法 2.建立和数据库服务器之间的连接&#xff0c;连接好了后&#xff…