【问题标题】:@SpringBootTest vs @ContextConfiguration vs @Import in Spring Boot Unit Test@SpringBootTest vs @ContextConfiguration vs @Import 在 Spring Boot 单元测试中
【发布时间】:2019-11-04 20:26:54
【问题描述】:

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

@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 (SpringBootTest Annotation) - 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 (ContextConfiguration Annotation) - 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 (Import Annotation) - 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);
    }

【问题讨论】:

    标签: spring-boot


    【解决方案1】:

    @MarkBramnik 的答案是我读过的关于 Spring 测试的最简单的答案。从 Spring 官方文档中,一切都已经说了,你的测试大约是 2 类:

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

    组成您的应用程序的 POJO 应该可以在 JUnit 或 TestNG 测试,使用 new 操作符实例化对象, 没有 Spring 或任何其他容器。您可以使用模拟对象(在 结合其他有价值的测试技术)来测试你的代码 孤立无援

    • Integration Tests :这需要您加载部分或全部上下文,因此您使用所有需要的 Spring 注释

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

    来自春天documentation

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

    真正的单元测试通常运行得非常快,因为没有 要设置的运行时基础架构。强调真正的单元测试作为一部分 您的开发方法可以提高您的生产力。你可以 不需要这部分测试章节帮你写 对基于 IoC 的应用程序进行有效的单元测试。对于某些单位 然而,测试场景,Spring 框架提供了模拟对象 和测试支持类,在本章中进行了描述。

    如果你的应用基于 Spring 推荐的架构(Respository > Service > Controller > etc...),你应该有以下规则(在我看来):

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

    【讨论】:

      【解决方案2】:

      我认为如果您的意图是运行适当的单元测试,那么所有 3 个选项都不好。 单元测试必须非常快,你应该能够在一秒钟左右运行数百个(当然取决于硬件,但你明白了)。 因此,一旦您说“我为每个测试开始 spring”-它就不再是单元测试了。 为每个测试启动 spring 是一项非常昂贵的操作。

      有趣的是,您的CoffeeService 代码以完全可测试的方式编写:只需使用 Mockito 之类的库来模拟存储库类,您就可以在没有任何弹簧的情况下测试服务逻辑。 您不需要任何弹簧跑步者,任何弹簧注释。您还会看到这些测试运行得更快。

      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(层 = 休息层、数据层等)。

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

      更新:

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

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

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

      不,不能,这是一个集成测试,在 spring 中只运行一个类。

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

      如果您使用 spring 运行一个类,那么无论如何,您都必须模拟它的所有依赖项,使用 mockito 也可以这样做......

      现在关于您的问题

      @ContextConfiguration 与 @SpringBootTest 的技术差异。

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

      • 配置管理,
      • 包结构,
      • 可插拔性
      • 日志记录
      • 数据库集成等

      所以 Spring Boot 建立了定义良好的流程来处理上述所有项目,如果你想启动将模仿 Spring Boot 应用程序的测试,那么你使用@SpringBootTest 注解。否则(或者如果您只有弹簧驱动的应用程序而不是弹簧靴) - 根本不要使用它。

      @ContextConfiguration 不过是完全不同的东西。它只是说你想在 Spring 驱动的应用程序中使用什么 bean(它也适用于 spring boot)

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

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

      【讨论】:

      • 这是单元测试与使用 Spring 上下文的集成测试的最佳答案之一。
      • 如果我们需要测试控制器和http响应代码会发生什么?这个答案没有意义。
      • @RamPrakash。最初的问题根本与控制器和 HTTP 无关……对于那些考虑使用 spring 提供的 MockMVC 的人,但又与原始问题完全无关
      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2018-08-20
        • 2017-11-05
        • 1970-01-01
        • 2018-06-28
        • 2010-09-10
        • 2016-05-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多