Spring Boot 单元测试中的@SpringBootTest、@ContextConfiguration、@Import

2024-04-05

我正在开发一个 Spring Boot 项目。 我正在写一个Unit Test代码基于TDD这有点困难。

@SpringBootTest加载所有 bean,这导致测试时间更长。

所以我用了@SpringBootTest的班级名称。

我正常完成了测试,但我不确定使用之间的区别@ContextConfiguration并使用@Import.

所有三个选项都运行正常。我想知道哪个选择是最好的。

@Service
public class CoffeeService {

    private final CoffeeRepository coffeeRepository;

    public CoffeeService(CoffeeRepository coffeeRepository) {
        this.coffeeRepository = coffeeRepository;
    }

    public String getCoffee(String name){
        return coffeeRepository.findByName(name);
    }
}

public interface CoffeeRepository {
    String findByName(String name);
}

@Repository
public class SimpleCoffeeRepository implements CoffeeRepository {

    @Override
    public String findByName(String name) {
        return "mocha";
    }
}

Option 1 using @SpringBootTest - OK

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {CoffeeService.class, SimpleCoffeeRepository.class})
public class CoffeeServiceTest {

    @Autowired
    private CoffeeService coffeeService;

    @Test
    public void getCoffeeTest() {
        String value = coffeeService.getCoffee("mocha");
        assertEquals("mocha", value);
    }
}

Option 2 using @ContextConfiguration - OK

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {SimpleCoffeeRepository.class, CoffeeService.class})
public class CoffeeServiceTest {

    @Autowired
    private CoffeeService coffeeService;

    @Test
    public void getCoffeeTest() {
        String value = coffeeService.getCoffee("mocha");
        assertEquals("mocha", value);
    }
}

Option 3 using @Import - OK

@RunWith(SpringRunner.class)
@Import({SimpleCoffeeRepository.class, CoffeeService.class})
public class CoffeeServiceTest {

    @Autowired
    private CoffeeService coffeeService;

    @Test
    public void getCoffeeTest() {
        String value = coffeeService.getCoffee("mocha");
        assertEquals("mocha", value);
    }

I think 如果您的目的是运行正确的单元测试,则所有 3 个提供的选项都不好。 单元测试必须非常快,您应该能够在一秒钟左右运行数百个单元测试(当然取决于硬件,但您明白了)。 所以一旦你说“我为每个测试启动 spring” - 它就不再是单元测试了。 每次测试启动弹簧都是一项非常昂贵的操作。

有趣的是你的代码CoffeeService是以完全可测试的方式编写的:只需使用像 Mockito 这样的库来模拟存储库类,您就可以在没有任何 spring 的情况下测试服务逻辑。 您不需要任何 Spring 运行程序、任何 Spring 注释。您还会发现这些测试运行得更快。

class MyServiceTest {

    @Test
    public void test_my_service_get_coffee_logic() {
          
           // setup:
           CoffeeRepository repo = Mockito.mock(CoffeeRepository.class);
           Mockito.when(repo.findByName("mocha")).thenReturn("coffeeFound");

           CoffeeService underTest = new CoffeeService(repo);

           // when:
           String actualCoffee  =  underTest.getCoffee("mocha");

           // then:
           assertEquals(actualCoffee, "coffeeFound");
    }
}
 

现在关于 spring 测试库

您可以将其视为一种测试代码的方法,该代码需要与其他组件进行一些互连,并且模拟所有内容是有问题的。它是同一 JVM 内的一种集成测试。 您提出的所有方法都运行应用程序上下文,这实际上是一件非常复杂的事情,youtube 上有完整的会话介绍应用程序上下文启动期间真正发生的情况 - 尽管超出了问题的范围,重点是执行上下文启动需要时间

@SpringBootTest更进一步,尝试模仿 Spring Boot 框架添加的用于创建上下文的过程:根据包结构决定要扫描的内容,从预定义位置加载外部配置(可选)运行自动配置启动器等等。

现在,可能加载应用程序中所有 bean 的应用程序上下文可能非常大,对于某些测试来说,这不是必需的。 这通常取决于测试的目的是什么

例如,如果您测试其余控制器(您已正确放置所有注释),则可能不需要启动数据库连接。

您提出的所有方法都过滤了到底应该运行什么、要加载什么bean 以及相互注入什么bean。

通常,这些限制适用于“层”而不是单个 bean(层 = 其余层、数据层等)。

第二种和第三种方法实际上是相同的,它们是“过滤”应用程序上下文仅保留必要的 bean 的不同方法。

Update:

由于您已经完成了这些方法的性能比较:

单元测试 = 非常快的测试,其目的是验证您编写的代码(当然也可以验证您的同事之一) 因此,如果您运行 Spring,它自动意味着一个相对较慢的测试。所以回答你的问题

使用@ContextConfiguration是否可以是“单元测试”

不,不能,这是一个集成测试,在春季仅运行一个类。

通常,我们不会只使用 Spring 框架运行一个类。如果只想测试一个类(一个单元)的代码,那么在 spring 容器内运行它有什么好处?是的,在某些情况下,它可以是几个类,但不是数十个或数百个。

如果你用 spring 运行一个类,那么在任何情况下,你都必须模拟它的所有依赖项,同样可以用mockito来完成......

现在关于您的问题

@ContextConfiguration 与 @SpringBootTest 技术差异。

@SpringBootTest仅当您有 Spring Boot 应用程序时才相关。该框架在底层使用 Spring,但简而言之,附带了许多关于如何编写应用程序的“基础设施”的预定义配方/实践:

  • 配置管理,
  • 封装结构,
  • 可插拔性
  • logging
  • 数据库集成等

因此 Spring Boot 建立了明确定义的流程来处理所有上述项目,如果您想启动模拟 Spring Boot 应用程序的测试,那么您可以使用@SpringBootTest注解。否则(或者如果您只有弹簧驱动的应用程序而没有弹簧引导) - 根本不要使用它。

@ContextConfiguration但这是完全不同的事情。它只是说明您想在 Spring 驱动的应用程序中使用哪些 bean(它也适用于 spring boot)

“单元测试”是使用@ContextConfiguration的正确方法吗?或不?

正如我所说 - 所有与 spring 测试相关的东西仅用于集成测试,所以不,这是在单元测试中使用的错误方法。对于单元测试,请使用根本不使用 spring 的东西(例如用于模拟的mockito 和没有 spring runner 的常规 junit 测试)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring Boot 单元测试中的@SpringBootTest、@ContextConfiguration、@Import 的相关文章

随机推荐