진짜 개발자
본문 바로가기

FrameWork/Spring Boot

Spring Boot - 스프링 부트 통합테스트 방법과 팁(Spring boot Integration Test)

728x90
Spring Boot 테스트

이번 포스팅에서는 Spring Boot에서 통합테스트하는 방법에 대해서 알아보려고 합니다.

 

https://medium.com/@ssowonny/%EC%84%A4%EB%A7%88-%EC%95%84%EC%A7%81%EB%8F%84-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1-%EC%95%88-%ED%95%98%EC%8B%9C%EB%82%98%EC%9A%94-b54ec61ef91a

테스트 코드의 중요성은 아무리 강조해도 부족합니다.

 

  


Code Link

https://gitlab.com/galid1/spring-boot-integration-test/-/tree/master/src/main/java/com/galid/jpastudy

 


 

목차

  1. 테스트 대상 어플리케이션 구축

    1.1 Dependency

    1.2 application.yml

    1.3 어플리케이션 코드

  2. 통합 테스트

    2.1 통합테스트 Base Class

    2.2 MockMvc 설명

    2.3 테스트 코드 팁

    2.4 테스트 코드

 



 

1. 테스트 대상 어플리케이션 구축

필요한 전체 클래스입니다.

 

테스트할 대상 어플리케이션의 기능은 다음과 같습니다.

  • 유저 생성
  • 유저 전체 조회
  • 하나의 유저 조회

 

 

1.1 Dependency

  • jpa

  • spring web

  • lombok

  • h2

  • boot starter test

    위 의존성을 추가합니다.

 

 

 

1.2 application.yml

Jpa 설정 및 테스트 db(h2) 설정을 진행합니다.

 

 

 

1.3 어플리케이션 코드

UserEntity

UserEntity 입니다. userName 필드를 가지고 있습니다.

 

 

UserRepository

UserRepository입니다. spring data jpa를 사용하여 CRUD 연산은 별도로 구현할 필요가 없습니다.

 

 

UserService

  • createUser
  • getUserList
  • getUser

세가지 기능을 가지고 있습니다.



UserController



 

 

Dto Class들

요청및 응답에 사용될 dto 클래스들 입니다.

 

 

하나의 데이터를 반환할때에도 클래스로 반환

 

 

 

 

 

 

2. 통합 테스트

단위 테스트로는 RequestMapping, Data Binding, Type Conversion, Validation, 등등을 커버할 수 없습니다. 따라서 코드 커버리지를 높이기 위해서는 통합테스트를 실시해야합니다.

 

장점

- 모든 빈을 컨테이너에 올리고 테스트 하기 때문에 운영환경과 유사한 환경에서 테스트를 할 수 있습니다.

- 통합테스트 이름 그대로, 전체적인 테스트를 진행할 수 있어, 코드 커버리지가 높아집니다.

 

단점

- 모든 빈을 컨테이너에 올리고 테스트 하기 때문에 시간이 오래걸립니다.

- 전체적인 테스트를 한번에 진행하기 때문에, 특정 계층 또는 특정 빈에서 발생하는 오류의 디버깅이 어렵습니다.

 

 

 

2.1 통합테스트 Base Class

모든 테스트 클래스의 부모가 될 기본 클래스입니다. MockMvc와 Object <-> String 변환을 위한 ObjectMapper를 필드 주입을 받아 가지고 있습니다.

 

@SpringBootTest

Spring boot가 아닌 Spring에서 test를 할때 사용하던 @ContextConfigruration 어노테이션의 대용 어노테이션으로, Test를 위한 Application Context를 로딩하며 여러가지 속성을 제공합니다.

- webEnvironment 속성

MOCK 으로 지정하는 경우, ApplicationContext를 로딩하며, embedded Server를 실행하지 않고, 가짜 웹 환경을 제공합니다.

실제 embedded server 실행을 원하는 경우, RANDOM_PORT 또는 DEFINED_PORT로 지정해야 합니다.

 

@Disabled

이 어노테이션을 지정하는 경우 해당 테스트 클래스 또는 테스트 메소드를 실행하지 않습니다. 위의 클래스의 경우 단지 설정을 상속하기 위한 클래스이기 때문에, 실행할 필요가 없습니다.

 

@Transactional

클래스 내부의 각각의 테스트 메소드가 실행될때마다, 데이터베이스를 롤백합니다. 데이터베이스에 의존성을 가지는 테스트코드를 대상으로 테스트 코드의 원칙중 하나인 반복가능한 테스트를 실현하기 위해 필요합니다.

@Transactional 롤백하지 않기

만약, 테스트 후 데이터를 직접 눈으로 보기 위해 데이터를 롤백하지 않고 싶다면, 메소드위에 @Rollback(false)을 부여하면 됩니다.

 

@AutoConfigureMockMvc

@WebMvcTest어노테이션을 사용하지 않는 경우 즉, @SpringBootTest 어노테이션을 사용하는 경우, MockMvc를 이용한 테스트를 진행하기 위해 필요한 어노테이션입니다.

@WebMvcTest어노테이션을 사용하는 경우, MockMvc는 자동으로 설정되며, 동시에 @AutoConfigureMockMvc를 사용하게 되면, 충돌이 나게됩니다.

 

*@WebMvcTest

@WebMvcTest 어노테이션은 Web 계층(Controller)만을 테스트할때 사용하는 어노테이션입니다. 스프링 컨테이너에 @Controller, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer 등과 같은 특정 빈들만을 등록하게 됩니다. 다른 계층의 빈들을 테스트 컨테이너에 올리지 않기 때문에, 보통 @MockBean과 함께 사용하게 됩니다.

프로젝트에서 spring security를 사용하는 경우, spring security 설정 또한, 자동으로 진행합니다.

 

 

 

2.2 MockMvc 설명

MockMvc는 스프링 mvc의 통합테스트를 위한 라이브러리입니다.

 

MockMvc.perform()

mvc.perform() 메소드는 MockMvcRequestBuilders를 매개변수로 받아, ResultActions를 return하는 메소드 입니다. MockMvcRequestBuilders를 반환하는 정적 메소드로는 post(), get(), put(), delete() 등이 존재합니다.

 

이 메소드들은 HttpRequest를 만들어내기 위한 Builder로써, header, body 등을 지정하는 메소드들이 존재하며 이들은 다시 MockMvcRequestBuilders를 반환하기 때문에, 간편하게 테스트를 위한 웹 요청을 만들수 있습니다.

 

 

ResultActions.andDo()

mockmvc 요청을 한뒤, 행동을 지정하는 메소드입니다. 결과를 출력한다던지(print()) 로그를 출력하는 등의 행동을 지정할 수 있습니다.

 

 

ResultActions.andExpect()

요청의 결과로 예상(원하는) 응답을 지정함으로 실질적으로 테스트를 진행합니다.

응답코드, 본문에 포함되는 데이터, 헤더, 쿠키, 세션 등 응답에 포함되는 전반적인 데이터들을 테스트할 수 있습니다.

 

 

 

2.3 테스트 코드 팁

given, when, then

- 테스트 코드 작성시, 많은 곳에서 추천하는 코딩 스타일인데요, 어떤값이 주어지고(given), 무엇을 했을때(when), 어떤 값을 원한다(then)을 나누어 직관적으로 볼 수 있기 때문에, 테스트 코드의 가독성이 향상됩니다.

- 테스트 코드의 가독성이 중요한 또 다른 이유는, 테스트 코드가 문서로써의 역할을 하기도 하기 때문입니다. 테스트코드를 봄으로써, 해당 메소드를 작성한 개발자가 어떤의도로 만들었으며, 어떻게 동작하길 원하는지를 알 수있습니다.

 

 

모든 response에 대한 테스트를 진행한다.

- api가(테스트대상) 조금이라도 수정될 경우, 테스트코드가 실패하게 됨으로써, 항상 올바른 테스트 코드를 유지할 수 있도록 돕습니다.

- api가(테스트대상) 변경되면, 테스트 코드역시 변경되어야 하는 것은 당연합니다.

- 테스트 코드는 커버리지가 높을 수록 좋습니다. 테스트 코드는, 정상적으로 작동하는 부분만을 테스트 하면 안됩니다, 테스트 코드는 실수나 오류를 발견하고 이를 줄이고 수정하기 위해 작성하는 것입니다.

 

 

회원 조회등을 위한 생성 메서드를 만든다.

통합테스트를 진행하면, 데이터베이스의 특정 데이터에 의존하는 테스트가 존재하기 마련입니다. 이때, 조회 메소드의 테스트에 집중하기위해, 특정 데이터를 생성하는 테스트 메소드를 별도로 만들어 호출하는 것으로, 테스트를 용이하게 진행합니다.

 

참고 - https://www.popit.kr/spring-guide-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%A0%84%EB%9E%B5/

 

 

 

2.4 테스트 코드

유저 생성 테스트

given 절에서, 유저 생성을 위한 요청 dto를 생성합니다.

when 절에서, 테스트할 api를 지정합니다(post("/users")). contentType()을 이용해, post 요청의 body에 담길 데이터의 타입을 지정하며, given절에서 생성한 요청을 objectMapper를 이용해 String으로 변환하여, content() 의 매개변수로 넘겨주어 요청 본문에 담길 데이터를 지정합니다.

then 절에서 기대하는 응답 코드를 지정합니다. status().isOk() 의 경우 응답으로 200코드를 기대합니다. 마지막으로, 응답 본문에 담긴 userId의 값이 null이 아님을 기대하는 코드를 작성합니다.

 

 

유저 조회 테스트

상단의 userSetUp은 데이터베이스에 의존하는 유저조회 테스트를 위한 보조 클래스입니다. saveUser() 메소드를 가지고 있으며 이는, 유저를 생성한 뒤 해당 유저의 id를 반환합니다.