Spring 引导中的单元测试或集成测试

Unit Test or Integration Test in Spring Boot

我在 Spring Boot 中查看了各种与测试相关的在线教程,并对测试的引用方式感到困惑。

一些文章将使用 @WebMvcTest 注释的控制器测试称为 Unit Test,而另一些文章将其称为 Integration Test。不确定哪个是正确的。

同样的问题适用于 @DataJpaTest 的存储库层测试。

我在我的应用程序中编写了以下两个测试,一个用于控制器,另一个用于存储库。

在底部,我对两者都有一些疑问。请指导。

UserControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserRepository userRepository;

    @Test
    public void signUp() throws Exception {
        this.mockMvc.perform(get("/signup")).andExpect(status().isOk());
    }

}

UserRepositoryTest.java

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;
    @Autowired
    private UserRepository userRepository;

    @Test
    public void whenFindByName_thenReturnEmployee() {
        // given
        User u = new User();
        u.setName("ab");
        u.setEmail("ab@cd.com");
        entityManager.persistAndFlush(u);
        // when
        Optional<User> user = userRepository.findById(1L);
        // then
        assertTrue(user.isPresent());
    }

}

我的问题是:

  1. 注释 @WebMvcTest, @DataJpaTest@SpringBootTest 是否决定了测试类型(UnitIntegration)还是在 @MockBean 中使用确定它的测试?
  2. 假设 UserControllerTest.java 是一个单元测试,我们在这里用 @MockBean private UserRepository userRepository 模拟 userRepository 依赖,而在 UserRepositoryTest.java 中我们用 @Autowired private UserRepository userRepository 自动装配它.为什么 ??

单元测试 运行 隔离,而集成测试 bootstrap Spring 执行开始前的 Web 上下文。

单元测试

运行 隔离有时会要求您根据正在测试的 class 模拟您的依赖项。通过这样做,您允许自己端到端地测试非常具体的测试用例,而不必担心服务或域层的开销。因此,使用 Mockito 或更具体地说,Mockito.mock() 方法模拟对象 classes 并且不会替换 Web 上下文中的任何对象,例如 @MockBean。

集成测试

然而,集成测试侧重于集成应用程序的不同层,例如数据库。关于数据库,大多数人使用内存数据库(如 H2)来测试他们的域 layers/repositories。集成测试不应该包含任何模拟,两种类型的测试应该 运行 分开。这并不是说集成测试不能包含任何模拟,但这种情况并不常见,因为您已经拥有独立的单元测试来测试包含模拟依赖项的应用程序的各个层!

E2E 测试

如果您要从端到端测试您的应用程序,最好不要模拟数据以外的任何内容,并进行适当的清理。 Cucumber 等测试框架非常适合端到端测试。你为什么要模拟不同的层,你已经有了这种类型的单元测试!

资源:https://www.baeldung.com/spring-boot-testinghttps://www.baeldung.com/java-spring-mockito-mock-mockbean

为什么需要spring做单元测试?您只能使用 Mockito 来执行此操作,而无需启动 spring 上下文。此处对此进行了详细解释和讨论:https://reflectoring.io/unit-testing-spring-boot/

在使用@MockBean 时,我也很困惑!这被认为是单元测试还是集成测试? 在我看来,即使我们使用的是模拟 bean,但我们仍然在 spring 上下文中 运行ning,对我来说这是一个集成测试(因为单元测试不需要任何 spring 上下文到 运行 内)。 Brandon 提到的同一站点将 @MockBean 视为集成测试 https://www.baeldung.com/java-spring-mockito-mock-mockbean

Image from above site

来自布兰登的回复:"Integration tests should not contain any mocking and both types of testing should be run separately."

如果你想测试一个从控制器一直到数据库的 api,但你想排除其他系统(如 kafka 或外部微服务)怎么办?你将如何实现这一目标?你绝对需要@MockBean。这是一个集成测试,即使它有模拟 beans。

总而言之(根据我的经验以及几天来搜索和阅读大量相互矛盾的信息之后)。这是我的看法:

  • 我会说,尽可能远离使用 spring 进行单元测试,而只是使用 Mockito 或其他不需要 spring 上下文的框架。例如,当为服务 class 编写测试以测试某些计算逻辑时,我们不需要 spring 上下文,这是一个纯单元测试。
  • 我们仍然可以为控制器 classes 编写纯单元测试。我们可以通过调用控制器中的方法来做到这一点,然后断言这些方法做了预期的事情(例如,使用正确的参数调用正确的底层方法等)。为服务编写单元测试的方式基本相同 class。 (如果已经涵盖在以下类型的测试中,也许不需要这些?)
  • 我们仍然可以在没有任何 spring 上下文的情况下为 api 编写纯单元测试。这描述了here。我试过了,它对我有用。我会将代码粘贴到 post 的末尾。
  • 当 运行在 spring 上下文中进行测试时,这被认为是 集成测试,即使您使用的是@MockBean。这方面的一个例子: 如果我们想测试一个 api 从控制器开始一直到 数据库,但我们想排除其他系统(如 kafka、电子邮件或 其他外部微服务)。我们将如何实现这一目标?我们 绝对需要@MockBean。这是一个集成测试,尽管它 使用一些模拟豆。
  • 我认为最令人困惑的部分是仅测试 api 层时 使用 spring 作为 UserControllerTest在问题中 (我 意思是调用 api 并确保它 returns 是正确的 状态代码和响应格式)。这被认为是单元测试还是 集成测试?它不是一个单元,因为单元测试不需要 spring 上下文到 运行 内。它实际上是介于 unit 和 集成测试。这个源很好地解释了这个概念 https://blog.marcnuri.com/mockmvc-spring-mvc-framework/(更具体地说是 MockMvc 独立设置)所以我认为, 然后返回到团队将这些测试放置在哪里(在单元中 test 文件夹,在集成测试文件夹中,在单独的文件夹中?) 还需要使用良好的命名约定来避免任何 与纯单元测试或纯集成测试混淆 class。据我所知,大多数团队都考虑那些单元测试,但我 我不确定这是否是最佳做法!

    //unit test to call an api using MockMvc and mockito only
    @RunWith(MockitoJUnitRunner.class)
    public class UserControllerTest {
    
    private MockMvc mockMvc;
    @Mock
    UserService userService;
    @InjectMocks
    UserController controllerUnderTest;
    
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(controllerUnderTest).build();
    }
    
    @Test
    public void testGetUser() throws Exception {
    
        //given:
        when(userService.getUser(.......)).thenReturn(....);
    
        //when:
        String url = "http://localhost:8081/api/ ....your url";
    
        //then:
        this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isOk());
    }
    

    }

希望对您有所帮助,如果有更好的意见请告诉我,因为我为此苦苦挣扎:)