본문 바로가기
Develop/Spring˙Spring Boot

[Spring Boot] 스프링 부트 테스트하기

by 독서왕뼝아리 2023. 2. 5.
다루는 내용
- 통합 테스트
- 서버에서 애플리케이션을 테스트하는 방법
- 스프링 부트 테스트 유틸리티

 

★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆

6년 전 기술이므로 현재 용어가 많이 바뀌었다.

흐름만 파악하고 공식문서를 참고하여 개발하도록하자.

https://spring.io/guides/gs/testing-web/

★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆

 

통합 테스트를 위한 자동 구성

스프링 애플리케이션 컨텍스트를 로드하고 테스트 클래스에 자동으로 주입하는 기능을 확성화하는 JUnit 클래스 러너인 SpringJUnit4ClassRunner를 제공하는 형태로 통합 테스트를 지원한다.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ReadingListApplication.class)
@WebAppConfiguration
public class ReadingListApplicationTests {
	@Test
	public void contextLoads() {
    	...
	}
}

- @RunWith: SpringJUnit4ClassRunner를 전달하여 스프링 통합 테스트를 활성화한다.

- @ContextConfiguration: 애플리케이션 컨텍스트를 어떻게 로드할지 지정한다. 하지만 스프링 부트 기능을 완전하게 로드하지 못한다. @SpringApplicationConfiguration로 바꿔 사용하도록 하자.

 

 

웹 애플리케이션 테스트하기

1. 스프링 Mock MVC

애플리케이션 서버를 구동하지 않고도 서블릿 컨테이너와 거의 비슷하게 작동하는 목 구현체로 컨트롤러를 테스트할 수 있다.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ReadingListApplication.class)
@WebAppConfiguration
public class MockMvcWebTests {

    @Autowired
    private WebApplicationContext webContext;

    private MockMvc mockMvc; //MockMvc 인스턴스 생성

    @Before
    public void setupMockMvc() {
        mockMvc = MockMvcBuilders // context주입
            .webAppContextSetup(webContext)
            .build();
    }
    
    @Test
    public void homePage() throws Exception { // GET 테스트
        mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(view().name("readingList"))
            .andExpect(model().attributeExists("books"))
            .andExpect(model().attribute("books", is(empty())));
    }

    @Test
    public void postBook() throws Exception { // POST 테스트
        mockMvc.perform(post("/") // POST 요청 수행
           .contentType(MediaType.APPLICATION_FORM_URLENCODED)
           .param("title", "BOOK TITLE")
           .param("author", "BOOK AUTHOR")
           .param("isbn", "1234567890")
           .param("description", "DESCRIPTION"))
           .andExpect(status().is3xxRedirection())
           .andExpect(header().string("Location", "/"));

        Book expectedBook = new Book(); // 생성할 책 정보 설정
        expectedBook.setId(1L);
        expectedBook.setReader("craig");
        expectedBook.setTitle("BOOK TITLE");
        expectedBook.setAuthor("BOOK AUTHOR");
        expectedBook.setIsbn("1234567890");
        expectedBook.setDescription("DESCRIPTION");

        mockMvc.perform(get("/")) // GET 요청을 실행해 새로 생성된 모델이 있는지 검증
           .andExpect(status().isOk()) // 정적 임포트 선언 후
           .andExpect(view().name("readingList"))
           .andExpect(model().attributeExists("books"))
           .andExpect(model().attribute("books", hasSize(1)))
           .andExpect(model().attribute("books", contains(samePropertyValuesAs(expectedBook))));
    }
    
}

Mock MVC를 설정하기 위해 MockMvcBuilders를 사용한다. 이 클래스는 정적 메서드 두 개를 제공한다.

- standaloneSetup() : 수동으로 생성하고 구성한 컨트롤러 한 개 이상을 서비스할 Mock MVC를 만든다.

- webAppContextSetup() : 컨트롤러 한 개 이상을 포함하는 스프링 애플리케이션 컨텍스트를 사용하여  Mock MVC를 만든다.

 

- @WebApplicationConfiguration: SpringJUnit4ClassRunner가 애플리케이션 컨텍스트로 기본값인 비웹용 ApplicationContext가 아니라 WebApplicationContext를 생성하도록 선언한다.

- @Before: 애너테이션을 붙여 다른 테스트 메서드보다 먼저 실행함을 나타낸다.

 

다음 명령으로 테스트를 수행한다.

$ gradle test

 

 

2. 웹 통합 테스트

톰캣, 제티 등 내장 서블릿 컨테이너에서 애플리케이션을 실행하여 실제 애플리케이션 서버에서 테스트할 수 있다.

 

스프링 시큐리티는 간단한 보안 웹 애플리케이션 테스트를 지원한다. 빌드에 스프링 시큐리티 테스트 모듈을 추가하자. 그레이들을 사용할 때는 testCompile을 추가하면 된다.(테스트 시에만 컴파일 된다는 의미)

testCompile 'org.springframework.security:spring-security-test'

 

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ReadingListApplication.class)
@WebAppConfiguration
public class MockMvcWebTests {

    @Autowired
    private WebApplicationContext webContext;

    private MockMvc mockMvc;

    @Before
    public void setupMockMvc() {
        mockMvc = MockMvcBuilders
            .webAppContextSetup(webContext)
            .apply(springSecurity())
            .build();
    }

    @Test
    public void homePage_unauthenticatedUser() throws Exception {
        mockMvc.perform(get("/"))
            .andExpect(status().is3xxRedirection())
            .andExpect(header().string("Location", "http://localhost/login"));
    }

    @Test
    @WithUserDetails("craig")
    public void homePage_authenticatedUser() throws Exception {
        Reader expectedReader = new Reader();
        expectedReader.setUsername("craig");
        expectedReader.setPassword("password");
        expectedReader.setFullname("Craig Walls");

        mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .andExpect(view().name("readingList"))
            .andExpect(model().attribute("reader", samePropertyValuesAs(expectedReader)))
            .andExpect(model().attribute("books", hasSize(0)))
            .andExpect(model().attribute("amazonID", "habuma-20"));
    }

}

springSecurity() 메서드는 Mock MVC용으로 스프링 시큐리티를 활성화하는 Mock MVC 구성자를 반환한다. 간단히 적용만 하면 MockMvc로 수행하는 모든 요청에 스프링 시큐리티가 적용된다. 

 

@Test
@WithMockUser(username="craig", password="password", roles="READER")
public void test() throws Exception {
    ...
}

- @WithMockUser: 지정한 사용자 이름, 패스워드, 권한으로  UserDetails를 생성한 후 보안 컨텍스트를 로드한다.

- @WithUserDetails: 지정한 사용자 이름으로 UserDetails 객체를 조회하여 보안 컨텍스트를 로드한다.

 

테스트를 수행하려고 어떤 서블릿 컨테이너도 시작한 적이 없다. 스프링 Mock MVC가 실제 서블릿 컨테이너를 대신한다. 그렇지만 이 테스트는 완전하지 않다.

 

 

실행 중인 애플리케이션 테스트하기

테스트 클래스에 @SpringBootTest (Spring Boot 1.2.1 이상 적용) 애너테이션을 붙이면 스프링 부트가 테스트용 애플리케이션 컨텍스트를 생성하면서 내장 서블릿 컨테이너도 시작한다. 애플리케이션을 내장 컨테이너와 함께 실행하고나면 이를 대상으로 실제 HTTP 요청을 보낼 수 있고 결과를 검증할 수 있다.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ReadingListApplication.class)
@SpringBootTest(randomPort = true)
public class SimpleWebTest {

    @Value("${local.server.port}")
    private int port;
	
	@Test(expected = HttpClientErrorException.class)
    public void pageNotFound() {
        try {
            RestTemplate rest = new RestTemplate();
            rest.getForObject("http://localhost:{port}/bogusPage", String.class, port);
            fail("Should result in HTTP 404");
        } catch (HttpClientErrorException e) {
            assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode());
            throw e;
        }
    }

}

 

 

테스트 방식이 지원하는 모듈에 따라 천차만별이고 자주 변화하므로 지속적인 공부가 필요하다...!

 

 

길벗 깃허브 : https://github.com/gilbutITbook/006859/tree/master/ch04