8장 스프링 테스트

Jasik·2022년 1월 30일
0

단위 테스트: 테스트할 클래스의 구현 내용만 테스트. 테스트할 클래스가 의존하는 다른 컴포넌트를 mock이나 stub으로 만들어 테스트 대상 클래스의 실행 결과가 다른 컴포넌트의 실행 내용에 좌지우지 되지 않게 한다.

통합 테스트: 목이나 스텁 사용하지 않고 실제 운영 환경에서 사용될 클래스를 통합해서 테스트. 스프링 테스트를 활용한 통합 테스트는 시스템이나 어플리케이션 전체가 의도한대로 정확하게 동작하는지 검증하는 것이 아니라, 개발자가 작성한 클래스가 스프링 프레임워크에서 정확하게 동작하는지 검증하는 테스트.

스프링 테스트는 주로 다음과 같은 기능을 제공.

  • JUnit, TestNG라는 테스팅 프레임워크를 사용해서 스프링의 DI 컨테이너를 동작시키는 기능
  • 트랜잭션을 태스트 상황에 맞게 제어하는 기능
  • 어플리케이션 서버를 사용하지 않고 스프링 MVC의 동작을 재현하는 기능
  • 테스트 데이터를 적재하기 위해 SQL을 실행하는 기능
  • RestTemplate을 이용해 HTTP요청에 대한 임의 응답을 보내는 기술

DI컨테이너와 빈 테스트

DI 컨테이너에서 관리되는 빈(@Controller, @Service, @Repository, @Component 등이 붙은 클래스)을 테스트 하는 방법.

junit dependency 추가

빈에 대한 단위 테스트

스프링의 DI 컨테이너 기능을 사용하지 않고 테스트 대상 클래스에서 구현한 로직만 테스트하는 것

public class MessageServiceTest {
    @Test
    public void testGetMessage() {
        MessageService service = new MessageService();
        String actualMessage = service.getMessage();
        assertThat(actualMessage, is("Hello!!"));
    }
}

MessageService는 내부적으로 MessageSource에 의존성을 가지고 있다.
단위 테스트 환경에서는 Mockito를 사용해 MessageSouce를 모의화(Mock)하여 테스트.

org.mockito.mockito-core dependency 추가.

@RunWith(MockitoJUnitRunner.class)
public class MessageServiceTest {
    @InjectMock
    MessageService service;
    
    @Mock
    MessageSource mockMessageSource;
    
    @Test
    public void testGetMessageByCode() {
        doReturn("Hello!!").when(mockMessageSource)
            .getMessage("greeting", null, Locale.getDefault());
        
        String actualMessage = service.getMessageByCode("greeting");
        assertThat(actualMessage, is("Hello!!"));
    }
}

DI컨테이너에서 관리되는 빈에 대한 통합 테스트

스프링 DI 컨테이너에 등록된 후 다른 컴포넌트까지 통합된 상태에서 더 테스트.
데이터베이스와 같은 외부 리소스의 접근까지 포함해서 테스트 하는 것이 바람직, 편의상 외부 시스템이나 외부 사이트와 연계되는 부분은 Mock이나 Stub으로 대체 가능.

@RunWith(SpringJUnit4ClassRunner.class) // DI컨테이너를 동작시키기 위한 Runner클래스 지정. 스프링 4.3부터 SpringJUnit4ClassRunner 별칭 클래스 SpringRunner 클래스 존재
@ContextConfiguration(class = AppConfig.class) // classes 속성에 DI 컨테이너가 사용하는 설정 클래스를 지정
public class MessageServiceIntegrationTest {
    @Autowired
    MessageService messageService; // DI컨테이너의 빈 인젝션
    
    @Test
    public void testGetMessageByCode() {
        String actualMessage = service.getMessageByCode("greeting");
        assertThat(actualMessage, is("Hello!!"));
    }
}

스프링 TestContext 프레임워크

테스팅 프레임워크에서 동작하는 테스트용 스프링 프레임워크의 기능.
스프링이 제공하는 애너테이션과 자바 표준 애너테이션, 스프링 테스트가 제공하는 테스트용 애너테이션 등을 사용해 테스트 케이스를 만들 수 있다.

스프링 테스트는 JUnit에서 스프링 TestContext 프레임워크를 동작시키기 위한 지원 클래스로 SpringJUnit4ClassRunner(SpringRunner) 클래스를 제공한다. @RunWith의 value 속성에 지정 (* @RunWith 에는 하나의 Runner 클래스만 지정 가능. Mockito가 제공하는 Runner등 다른 Runner클래스와 함께 사용할 수 없다. 다른 러너와 스프링 TestContext 프레임워크를 함께 사용하려면 SpringClassRule/SpringMethodRule 를 활용)

스프링 TestContext 프레임워크에 DI컨테이너를 생성하려면 @org.springframework.test.context.ContextConfiguration을 테스트 케이스에 붙여주고 해당 애너테이션에 classes 속성이나 locations 속성에 빈 정의 파일을 지정.

@ContextConfiguration(classes = AppConfig.class)
public class MessageServiceIntegrationTest {
    // ...
}

classes 속성이나 locations 속성을 생략하는 경우

@RunWith(SpringRunner.class)
@ContextConfiguration
public class MessageServiceIntegrationTest {
    @Configuration
    static class LocalContext {
        // ...
    }
}

내부 클래스로 정의된 static 설정 클래스의 정보를 사용.

웹 애플리케이션의 테스트 환경 설정

@org.springframework.test.context.ContextConfiguration 말고 @org.springframework.test.context.web.WebAppConfiguration 을 사용하는 방법도 있다. 이 방법으로 웹 애플리케이션 전용 DI컨테이너 (WebApplicationContext)를 만들 수 있다.
@WebAppConfiguration을 사용하면 프로젝트의 src/main/webapp 디렉터리가 웹 애플리케이션의 루트 디렉터리로 인식된다. 이는 메이븐이나 그레이들이 정한 표준 웹앱 루트 디렉토리와 같다.
서블릿 API를 사용한 각종 목 객체(MockServletContext, MockHttpSession, MockHttpServletRequest, MockHttpServletResponse)도 테스트케이스에 주입해서 활용 가능.

프로파일 지정

스프링의 프로파일 기능을 사용한 애플리케이션은 @ActiveProfiles 를 사용해 테스트한다.

테스트용 프로퍼티 값 지정

시스템 프로퍼티(자바 VM의 -D 옵션)나 프로퍼티 파일에서 가져오는 클래스가 있다면 프로퍼티 값을 바꿔보며 테스트할 필요가 있음.
@TestPropertySource 활용.

Test 클래스에
@TestPropertySource(properties = "auth.failureCountToLock=3") 또는@TestPropertySource(locations = "/test.properties") 를 사용하여 프로퍼티 값 지정 가능.

데이터베이스 테스트

데이터베이스에 접근하는 빈을 테스트할 때 필요한 작업

  • 테스트용 데이터 소스 설정
  • 테스트 데이터 적재
  • 테스트 케이스용 트랜잭션 제어
  • 데이터 검증

테스트 데이터 소스 설정

테스트용 데이터 소스 정의 예시

@Configuration
public class TestConfig {
    @Bean
    public DataSource dataSource() { // 실제로 사용할 데이터 소스의 빈과 같은 이름을 사용
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .setScriptEncoding("UTF-8")
            .addScript("schema.sql")
            .build();
    }
}
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class AccountRepositoryTest {
    // ...
}

@ContextConfiguration에 빈 정의 파일을 지정할 때는 실제로 사용할 빈 정의 파일을 먼저 지정한 다음 테스트용 빈 정의 파일을 지정한다.

테스트 데이터 적재

@org.springframework.test.context.jdbc.Sql 을 사용.
테스트 케이스의 메서드를 호출하기 전에 임의로 SQL 실행 가능. SQL이 실행되는 타이밍을 테스트 케이스의 메서드가 종료된 후로 변경 가능.
@Sql 을 커스터마이징 하려면 config 속성에 SqlConfig를 지정.

@Sql은 클래스와 메서드에 모두 적용 가능. 둘 다 적용된 경우 메서드에 적용된 것이 우선.

// ...
@Sql("/account-delete.sql")
public class AccountRepositoryTest {
    ...
    
    @Test // account-delete.sql이 실행된다.
    public void testCreate() {
        // ...
    }
    
    // account-delete.sql와 account-insert-data.sql 이 실행된다.
    @Test
    @Sql({"/account-delete.sql", "/account-insert-data.sql"})
    public void testFindOne() {
        // ...
    }
}

테스트 케이스를 위한 트랜잭션 제어

같은 데이터베이스를 여러 테스트 환경에서 공유한다면 JUnit을 실행하는 와중에 또 다른 테스트에서 데이터를 변경할 수도 있기 때문에 각별한 주의 필요.
스프링 테스트가 제공하는 테스트용 트랜잭션 기능을 활용하여 해결 가능.

트랜잭션 경계의 이동

트랜잭션 경계를 테스트 케이스의 메서드 실행 전으로 이동하려면 스프링이 제공하는 @Transactional을 클래스나 메서드에 붙여주면 된다.

트랜잭션 경계에서 롤백과 커밋의 제어

@Transactional을 이용해서 트랜잭션 경계를 테스트 케이스의 메서드 실행 전으로 이동시켰다면, 기본적으로 테스트가 종료될 때 트랜잭션이 롤백된다.
커밋해야하는 경우가 있다면 @org.springframework.test.annotation.Commit 을 클래스나 메서드에 지정.

@Transactional
@Commit // 클래스에 @Commit 지정
public class AccountRepositoryTest {
    @Test // 커밋됨
    public void testCreate1() {
        // ...
    }
    
    @Test
    @Rollback // 롤백됨
    public void testCreate2() {
        // ...
    }
}

데이터 검증

JdbcTemplate을 사용하여 검증 가능.
예를들어 삽입한 데이터가 잘 삽입 되었는지 확인하기 위해 repository에 삽입을 하고, 삽입한 데이터의 키를 통해 JdbcTemplate으로 데이터베이스에서 방금 삽입한 데이터를 조회한 후 조회한 데이터의 유효성을 검증하면 된다.

@Transactional을 사용하는 경우 JdbcTemplate에서 사용하는 DataSource와 테스트 대상 컴포넌트에서 사용하는 DataSource가 같아야 한다.

스프링 MVC 테스트

컨트롤러의 테스트는 스프링 MVC의 프레임워크 기능까지 통합된 상태인 통합 테스트의 관점으로 보는 것이 맞다.

전통적인 방법은 웹 애플리케이션을 애플리케이션 서버에 배포하고 E2E(End to end)로 테스트 하는 방법이다. 다음과 같은 단점들 존재.

  • 애플리케이션 서버나 데이터베이스를 반드시 가동해야한다
  • 트랜잭션이 커밋되기 때문에 테스트를 실시하기 이전의 상태로 되돌릴 수 없다
  • 회귀 테스트를 실행하기 위해 Selenium 등을 활용해 테스트 케이스를 구현해야 한다.

스프링 테스트는 E2E테스트의 단점을 해소하면서 스프링 MVC와 통합한 상태인 컨트롤러를 테스트하기 위해 org.springframework.test.web.servlet.MockMvc 클래스를 제공.

MockMvc

웹 애플리케이션을 서버에 배포하지 않고도 스프링 MVC의 동작을 재현할 수 있는 클래스.

흐름
1. 테스트 케이스의 메서드는 DispatcherServlet에 요청할 데이터(요청 경로나 요청 파라미터 등)를 설정.
2. MockMvc는 DispatcherServlet에 요청을 보냄. 이 때 사용하는 DispatcherServlet은 테스트용으로 확장된 org.springframework.test.web.servlet.TestDispatcherServlet
3. DispatcherSerlvet은 요청을 받아 매핑 정보를 보고 그에 맞는 핸들러 메서드를 호출.
4. 테스트 케이스 메서드는 MockMvc가 반환하는 실행 결과를 받아 실행 결과가 맞는지 검증.

profile
가자~

0개의 댓글

관련 채용 정보