@SpringBootTest vs @ContextConfiguration vs @Import 在 Spring



我正在做一个Spring Boot项目。 我正在编写一个基于TDDUnit Test代码,这有点困难。

@SpringBootTest加载了所有豆子,这导致了更长的测试时间。

所以我使用了@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";
}
}

选项 1使用@SpringBootTest- 确定

@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);
}
}

选项 2使用@ContextConfiguration- 确定

@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);
}
}

选项 3使用@Import- 确定

@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);
}

如果您打算运行适当的单元测试,我认为所有 3 个呈现的选项都是不好的。 单元测试必须非常快,您应该能够在一秒钟左右运行数百个单元测试(当然取决于硬件,但您明白了)。 因此,一旦你说"我每次测试都开始春天"——它就不再是单元测试了。 每次测试的启动弹簧都是非常昂贵的操作。

有趣的是,你的CoffeeService代码是以一种完全可测试的方式编写的:只需使用像 Mockito 这样的库来模拟存储库类,您就可以在没有任何 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");
}
}

现在关于弹簧测试库

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

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

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

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

您介绍的所有方式都过滤了应该运行的内容,要加载和相互注入的豆子。

通常,这些限制应用于"层",而不是单个 bean(层 = 静止层、数据层等)。

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

更新:

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

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

使用@ContextConfiguration是否可以是"单元测试">

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

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

如果你用 spring 运行一个类,那么无论如何,你都必须模拟它的所有依赖项,同样的事情也可以用 mockito 完成......

现在关于您的问题

@ContextConfiguration与@SpringBootTest技术差异。

仅当您有 Spring Boot 应用程序时,@SpringBootTest才相关。这个框架在引擎盖下使用 Spring,但简而言之,它附带了许多预定义的配方/实践,介绍如何编写应用程序的"基础设施":

  • 配置管理,
  • 包结构,
  • 可插拔性
  • 伐木
  • 数据库集成等

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

不过,@ContextConfiguration是完全不同的事情。它只是说你想在弹簧驱动的应用程序中使用什么豆子(它也适用于弹簧启动)

"单元测试"是使用@ContextConfiguration的正确方法吗?要不?

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

@MarkBramnik的答案是我在春季测试中读过的最简单的答案。从 Spring 官方文档中,一切都已经说了,您的测试大约有 2 个类别:

  • Unit Tests:不需要加载 Spring 上下文。此时,你不需要任何Spring TestContext Framework注释。您可以简单地使用,JUnit,TestNG,Mockito等...

组成应用程序的 POJO 应该可以在 JUnit 中进行测试,或者 TestNG 测试,使用新运算符实例化对象, 没有弹簧或任何其他容器。您可以使用模拟对象(在 结合其他有价值的测试技术)来测试您的代码 孤立地

  • Integration Tests:这需要你加载一个部分或所有上下文,所以你使用所有需要的 Spring 注释

但是,对于某些单元测试场景,Spring 框架 提供模拟对象和测试支持类,如下所述 在本章中

来自 Spring 文档:

依赖注入应该使你的代码减少对 容器比传统的Java EE开发要好。这 构成应用程序的 POJO 应该可在 JUnit 中测试,或者 TestNG 测试,使用新运算符实例化对象, 没有弹簧或任何其他容器。您可以使用模拟对象(在 结合其他有价值的测试技术)来测试您的代码 孤立地。如果遵循以下体系结构建议: 弹簧,由此产生的干净分层和组件化 代码库有助于更轻松地进行单元测试。例如,您可以测试 通过存根或模拟 DAO 或存储库的服务层对象 接口,无需在运行时访问持久数据 单元测试。

真正的单元测试通常运行得非常快,因为没有 要设置的运行时基础结构。强调真正的单元测试是一部分 您的开发方法可以提高您的生产力。你可以 不需要测试章节的这一部分来帮助你编写 针对基于 IoC 的应用程序进行有效的单元测试。对于某些单位 测试场景,但是,Spring 框架提供了模拟对象 以及测试支持类,本章将对此进行介绍。

如果你的应用程序是基于 Spring 推荐架构(存储库>服务>控制器>等),你应该有以下规则(在我看来):

  • 测试存储库:使用@DataJpaTest注释进行测试切片。
  • 测试您的服务层:使用JUnitMockito。在这里,您将模拟您的存储库
  • 测试控制器层:使用@WebMvcTest注释进行测试切片或使用JUnitMockito。在这里,您将在这两种情况下模拟您的服务
  • 测试组件,例如第三方库包装器或加载一些特定的 bean:使用@ExtendWith(SpringExtension.class)@ContextConfiguration/@Import@SpringJUnitWebConfig这是两者的组合。
  • 测试与LDAP或任何外部API等的集成:使用@DataLdapTest测试切片或关联的注释,或者只是使用WireMock或任何模拟工具模拟它。
  • 做一个集成测试:使用@SpringBootTest

就像@MarkBramnik说的那样,如果你打算编写一个单元测试,你必须模拟使用你正在测试的特定组件的其他组件。 如果要编写模拟应用程序进程的集成测试,建议使用@SpringBootTest。 当您在单元测试中@Autowired组件并且必须设置该类的配置或创建 Bean 的类时,将使用@ContextConfiguration

最新更新