1. 개요

이 튜토리얼에서는 Spring Boot의 프레임 워크 지원을 사용하여 테스트를 작성 하는 방법을 살펴 봅니다 . 테스트를 실행하기 전에 Spring 컨텍스트를 부트 스트랩하는 통합 테스트뿐만 아니라 격리 된 상태에서 실행할 수있는 단위 테스트를 다룰 것입니다.

Spring Boot를 처음 사용하는 경우 Spring Boot 소개를 확인하십시오 .

2. 프로젝트 설정

이 기사에서 사용할 애플리케이션은 직원 리소스 에 대한 몇 가지 기본 작업을 제공하는 API입니다 . 이는 일반적인 계층 형 아키텍처입니다. API 호출은 컨트롤러 에서 서비스 , 지속성 계층으로 처리됩니다.

3. Maven 의존성

먼저 테스트 의존성을 추가해 보겠습니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <version>2.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>

스프링 부팅 선발 시험은 우리의 테스트에 필요한 요소의 대부분을 포함하는 주요 종속이다.

H2 DB는 우리의 인 메모리 데이터베이스입니다. 테스트 목적으로 실제 데이터베이스를 구성하고 시작할 필요가 없습니다.

3.1 JUnit 4

Spring Boot 2.4부터 JUnit 5의 빈티지 엔진이 spring-boot-starter-test 에서 제거되었습니다 . 여전히 JUnit 4를 사용하여 테스트를 작성하려면 다음 Maven 의존성을 추가해야합니다.

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

4. @SpringBootTest를 사용한 통합 테스트

이름에서 알 수 있듯이 통합 테스트는 애플리케이션의 여러 계층을 통합하는 데 중점을 둡니다. 그것은 또한 조롱이 포함되지 않음을 의미합니다.

이상적으로는 통합 테스트를 단위 테스트와 분리하여 유지해야하며 단위 테스트와 함께 실행해서는 안됩니다. 통합 테스트 만 실행하기 위해 다른 프로필을 사용하여이를 수행 할 수 있습니다. 이를 수행하는 몇 가지 이유는 통합 테스트에 시간이 많이 걸리고 실행하는 데 실제 데이터베이스가 필요할 수 있기 때문입니다.

그러나이 기사에서는 그것에 초점을 맞추지 않고 대신 메모리 내 H2 지속성 저장소를 사용할 것입니다.

통합 테스트는 테스트 케이스를 실행하기 위해 컨테이너를 시작해야합니다. 따라서이를 위해 몇 가지 추가 설정이 필요합니다.이 모든 것은 Spring Boot에서 쉽습니다.

@RunWith(SpringRunner.class)
@SpringBootTest(
  SpringBootTest.WebEnvironment.MOCK,
  classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
  locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // write test cases here
}

@SpringBootTest 우리는 전체 컨테이너 부트 스트랩해야 할 때 어노테이션에 유용합니다. 어노테이션은 테스트에서 활용 될 ApplicationContext생성함으로써 작동 합니다.

우리는 사용할 수 webEnvironment의 의 속성 @SpringBootTest을 우리의 런타임 환경을 구성; 컨테이너가 모의 서블릿 환경에서 작동하도록 여기에서 WebEnvironment.MOCK을 사용 하고 있습니다.

다음으로 @TestPropertySource 어노테이션은 테스트에 특정한 속성 파일의 위치를 ​​구성하는 데 도움이됩니다. @TestPropertySource로 로드 된 속성 파일 은 기존 application.properties 파일을 재정의 합니다.

application-integrationtest.properties는 지속성 스토리지를 구성하는 세부 사항을 포함 :

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

MySQL에 대한 통합 테스트를 실행하려면 속성 파일에서 위의 값을 변경할 수 있습니다.

통합 테스트의 테스트 케이스는 컨트롤러 계층 단위 테스트 와 유사 할 수 있습니다 .

@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
  throws Exception {

    createTestEmployee("bob");

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content()
      .contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$[0].name", is("bob")));
}

컨트롤러 계층 단위 테스트 와의 차이점은 여기서는 모의 대상이 없으며 종단 간 시나리오가 실행된다는 것입니다.

5. @TestConfiguration으로 구성 테스트 

이전 섹션에서 보았 듯이 @SpringBootTest 어노테이션이 달린 테스트 는 전체 애플리케이션 컨텍스트를 부트 스트랩합니다. 즉, 구성 요소 스캔에서 선택한 모든 빈을 테스트로 @Autowire 할 수 있습니다 .

@RunWith(SpringRunner.class)
@SpringBootTest
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // class code ...
}

그러나 실제 애플리케이션 컨텍스트를 부트 스트랩하는 것을 피하고 특별한 테스트 구성을 사용하고 싶을 수 있습니다. @TestConfiguration 어노테이션으로 이를 달성 할 수 있습니다  . 어노테이션을 사용하는 방법에는 두 가지가 있습니다. @Autowire 하려는 동일한 테스트 클래스의 정적 내부 클래스 중 하나입니다 .

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeService() {
                // implement methods
            };
        }
    }

    @Autowired
    private EmployeeService employeeService;
}

또는 별도의 테스트 구성 클래스를 만들 수 있습니다.

@TestConfiguration
public class EmployeeServiceImplTestContextConfiguration {
    
    @Bean
    public EmployeeService employeeService() {
        return new EmployeeService() { 
            // implement methods 
        };
    }
}

@TestConfiguration 어노테이션이 달린 구성 클래스  는 구성 요소 스캔에서 제외되므로 @Autowire를 원하는 모든 테스트에서 명시 적으로 가져와야 합니다. @Import  어노테이션 을 사용하여이를 수행 할 수 있습니다  .

@RunWith(SpringRunner.class)
@Import(EmployeeServiceImplTestContextConfiguration.class)
public class EmployeeServiceImplIntegrationTest {

    @Autowired
    private EmployeeService employeeService;

    // remaining class code
}

6. @MockBean으로 Mocking 하는방법

우리의 서비스 계층 코드는 우리에 의존 저장소 :

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

그러나 서비스 계층 을 테스트하기 위해 지속성 계층이 구현되는 방식을 알거나 신경 쓸 필요가 없습니다. 이상적으로는 전체 지속성 계층에 연결하지 않고도 서비스 계층 코드 를 작성하고 테스트 할 수 있어야합니다 .

이를 위해 Spring Boot Test에서 제공하는 모의 지원을 사용할 수 있습니다.

먼저 테스트 클래스 스켈레톤을 살펴 보겠습니다.

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {
 
        @Bean
        public EmployeeService employeeService() {
            return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // write test cases here
}

Service 클래스 를 확인하려면 테스트 클래스에서 @Autowire수 있도록 @Bean 으로 생성되고 사용 가능한 Service 클래스 의 인스턴스가 있어야합니다 . @TestConfiguration 어노테이션을 사용하여이 구성을 수행 할 수 있습니다 .

여기서 또 다른 흥미로운 점은 @MockBean 의 사용입니다 . 모의을 생성 위한 EmployeeRepository 실제 호출 우회하는데 사용될 수 EmployeeRepository :

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

설정이 완료되었으므로 테스트 케이스가 더 간단 해집니다.

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);
 
     assertThat(found.getName())
      .isEqualTo(name);
 }

7. @DataJpaTest를 사용한 통합 테스트

ID이름 을 속성으로 갖는 Employee 라는 엔티티로 작업 할 것입니다 .

@Entity
@Table(name = "person")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Size(min = 3, max = 20)
    private String name;

    // standard getters and setters, constructors
}

다음은 SpringData JPA를 사용하는 저장소입니다.

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    public Employee findByName(String name);

}

이것이 지속성 레이어 코드입니다. 이제 테스트 클래스를 작성해 보겠습니다.

먼저 테스트 클래스의 골격을 만들어 보겠습니다.

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

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private EmployeeRepository employeeRepository;

    // write test cases here

}

@RunWith (SpringRunner.class) 는 Spring Boot 테스트 기능과 JUnit을 연결합니다. JUnit 테스트에서 Spring Boot 테스트 기능을 사용할 때마다이 어노테이션이 필요합니다.

@DataJpaTest 는 지속성 계층을 테스트하는 데 필요한 몇 가지 표준 설정을 제공합니다.

  • 메모리 내 데이터베이스 인 H2 구성
  • Hibernate, Spring Data 및 DataSource 설정
  • @EntityScan 수행
  • SQL 로깅 켜기

DB 작업을 수행하려면 데이터베이스에 이미 일부 레코드가 필요합니다. 이 데이터를 설정하기 위해 TestEntityManager 를 사용할 수 있습니다 .

Spring Boot TestEntityManager  는 테스트를 작성할 때 일반적으로 사용되는 메소드를 제공 하는 표준 JPA EntityManager 의 대안 입니다.

EmployeeRepository 는 테스트 할 구성 요소입니다.

이제 첫 번째 테스트 케이스를 작성해 보겠습니다.

@Test
public void whenFindByName_thenReturnEmployee() {
    // given
    Employee alex = new Employee("alex");
    entityManager.persist(alex);
    entityManager.flush();

    // when
    Employee found = employeeRepository.findByName(alex.getName());

    // then
    assertThat(found.getName())
      .isEqualTo(alex.getName());
}

위의 테스트에서는 TestEntityManager사용하여 직원 을 DB 에 삽입하고 이름으로 찾기 API를 통해 읽습니다.

assertThat (...) 부분은에서 온다 Assertj 라이브러리 Spring 부팅과 함께 번들로 제공됩니다.

8. @WebMvcTest를 사용한 단위 테스트

우리의 컨트롤러 에 따라 서비스 층; 단순성을 위해 단일 메서드 만 포함하겠습니다.

@RestController
@RequestMapping("/api")
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees() {
        return employeeService.getAllEmployees();
    }
}

컨트롤러 코드 에만 집중하기 때문에 단위 테스트를 위해 서비스 레이어 코드 를 모의하는 것이 당연합니다 .

@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    // write test cases here
}

컨트롤러 를 테스트하기 위해 @WebMvcTest 를 사용할 수 있습니다 . 단위 테스트를 위해 Spring MVC 인프라를 자동 구성합니다.

대부분의 경우 @ WebMvcTest 는 단일 컨트롤러를 부트 스트랩하도록 제한됩니다. 필요한 의존성에 대한 모의 구현을 제공하기 위해 @MockBean 과 함께 사용할 수도 있습니다 .

@WebMvcTest 또한 자동 구성합니다 MockMvc 전체 HTTP 서버를 시작하지 않고 쉽게 테스트 MVC 컨트롤러의 강력한 방법을 제공합니다.

그런 다음 테스트 케이스를 작성해 보겠습니다.

@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
  throws Exception {
    
    Employee alex = new Employee("alex");

    List<Employee> allEmployees = Arrays.asList(alex);

    given(service.getAllEmployees()).willReturn(allEmployees);

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$[0].name", is(alex.getName())));
}

GET (...) 메소드 호출은 같은 HTTP 동사에 해당하는 다른 방법으로 대체 될 수 넣어 () , 후 () 등, 우리는 또한 요청의 내용 유형을 설정하는 것을 바랍니다 메모를.

MockMvc 는 유연하며이를 사용하여 모든 요청을 생성 할 수 있습니다.

9. 자동 구성 테스트

Spring Boot의 자동 구성 어노테이션의 놀라운 기능 중 하나는 전체 애플리케이션의 일부를로드하고 코드베이스의 테스트 별 레이어를로드하는 데 도움이된다는 것입니다.

위에서 언급 한 어노테이션 외에도 널리 사용되는 어노테이션 List은 다음과 같습니다.

  • @WebF luxTest : @WebFluxTest 어노테이션을 사용하여 Spring WebFlux 컨트롤러를 테스트 할 수 있습니다  .  필요한 의존성에 대한 모의 구현을 제공하기 위해 @MockBean 과 함께 자주 사용됩니다 .
  • @JdbcTest : W e는 @JdbcTest 어노테이션을 사용하여 JPA 애플리케이션을 테스트 할 수 있지만 DataSource 만 필요한 테스트 용입니다 . 어노테이션은 인 메모리 임베디드 데이터베이스와 JdbcTemplate을 구성합니다.
  • @JooqTest : jOOQ 관련 테스트를 테스트하기 위해 DSLContext 를 구성하는 @JooqTest 어노테이션을 사용할 수 있습니다  .
  • @DataMongoTest : MongoDB 애플리케이션을 테스트하기 위해 @DataMongoTest 는 유용한 어노테이션입니다. 기본적으로 의존성을 통해 드라이버를 사용할 수있는 경우 메모리에 내장 된 MongoDB를 구성하고, MongoTemplate을 구성하고 , @Document 클래스를 스캔하고, SpringData MongoDB 저장소를 구성합니다.
  • @DataRedisTest를 사용하면 Redis 애플리케이션을보다 쉽게 ​​테스트 할 수 있습니다. @RedisHash 클래스를 스캔하고 기본적으로 SpringData Redis 저장소를 구성합니다.
  • @DataLdapTest  는 인 메모리 임베디드 LDAP (사용 가능한 경우)를 구성하고, LdapTemplate을 구성하고 , @Entry 클래스를 스캔하고 , 기본적으로 SpringData LDAP 저장소를 구성 합니다.
  • @RestClientTest : 일반적으로 @RestClientTest 어노테이션을 사용하여 REST 클라이언트를 테스트합니다. Jackson, GSON 및 Jsonb 지원과 같은 다양한 의존성을 자동 구성합니다. RestTemplateBuilder를 구성합니다 . 기본적으로 MockRestServiceServer 대한 지원을 추가합니다 .
  • @JsonTest : JSON 직렬화를 테스트하는 데 필요한 Bean으로 만 Spring 애플리케이션 컨텍스트를 초기화합니다.

이러한 어노테이션과 통합 테스트를 최적화하는 방법에 대한 자세한 내용은 Spring Integration Tests 최적화 문서에서 확인할 수 있습니다 .

10. 결론

이 기사에서는 Spring Boot의 테스트 지원에 대해 자세히 알아보고 단위 테스트를 효율적으로 작성하는 방법을 보여주었습니다.

이 기사의 전체 소스 코드는 GitHub에서 찾을 수 있습니다 . 소스 코드에는 더 많은 예제와 다양한 테스트 케이스가 포함되어 있습니다.

테스트에 대해 계속 배우고 싶다면 통합 테스트 , Spring 통합 테스트 최적화JUnit 5의 단위 테스트관련된 별도의 기사가 있습니다 .