1. 테스트 코드 작성 이유
테스트 코드를 작성하는 이유는 여러가지가 있습니다. 코드의 안정성을 높이고, 기능을 추가하거나 수정하면서 발생하는 부작용(Side-Effect)를 줄일 수 있습니다. 그렇기 때문에 기존 코드를 수정하는 것에 대한 불안감을 줄일 수 있고, 결과적으로 생산성을 높여줄 수 있습니다. 또한 더 깔끔하고 좋은 코드를 작성하게 해줍니다.
2. F.I.R.S.T 단위 테스트 원칙
단위 테스트는 가장 작은 단위의 테스트입니다. 단위 테스트만 구성되어도 굉장히 많은 문제를 해결할 수 있습니다.
- Fast : 테스트 코드를 실행하는 일은 오래 걸리면 안된다.
- Independent:독립적으로 실행이 되어야 한다.
- Repeatable : 테스트는 실행할 때마다 같은 결과를 만들어야 한다.
- Self-validating : 메뉴얼 없이 테스트 코드만 실행해도 성공, 실패 여부를 알 수 있어야 한다.
- Timely : 유닛 테스트는 프로덕션 코드가 테스트를 성공하기 직전에 구성되어야 한다. 즉, 바로 사용가능해야 한다.
3. 스프링부트 테스트 디펜던시
스프링부트는 애플리케이션 테스트를 위한 많은 기능을 제공합니다. 크게 두 가지 모듈을 제공합니다.
- spring-boot-test : 핵심 기능 포함
- spring-boot-test-autoconfigure : 테스트를 위한 AutoConfiguration 제공
스프링부트 테스트 관련 애노테이션으로는 다음과 같은 것들이 있습니다.
어노테이션 | 설명 | Bean |
@SpringBootTest | 통한 테스트 | Bean 전체 |
@WevMvcTest | 단위테스트, MVC 테스트 | MVC 관련된 Bean |
@DataJpaTest | 단위 테스트, JPA 테스트 | JPA관련 Bean |
@RestClientTest | 단위 테스트, Rest API 테스트 | 일부 Bean |
@JsonTest | 단위 테스트, Json 테스트 | 일부 Bean |
@SpringBootTest
통합 테스트를 위해 제공하는 기본적인 스프릉부트 테스트 어노테이션입니다. 여러 단위 테스트를 하나의 통합된 테스트로 수행할 때 적합합니다. 실제 구동되는 애플리케이션과 똑같이 컨텍스트를 로드하여 모든 Bean을 등록하고 테스트를 하기 때문에 애플리케이션의 규모가 클수록 느려집니다. JUnit의 SpringJUnit4ClassRunner 클래스를 상속 받는 @RunWith(SpringRynnver.class)를 꼭 붙여서 사용해야 정상동작합니다.
4. 스프링부트 단위 테스트
스프링부트 환경에서 단위 테스트를 작성해보겠습니다. @SpringBootTest 어노테이션을 사용하지 않고, 즉, 스프링을 전혀 실행하지 않는다는 얘기입니다. @SpringBootTest는 스프링을 실행시켜야하기 때문에 단위 테스트가 아니라고 보지만 다른 책에서는 단위 테스트라고 언급하기도 합니다.
아래와 같은 Repository와 Service 객체가 있습니다.
@Repository
public class CoffeRepository {
private Map<String, Coffee> coffeeMap = new HashMap<>();
public Coffee findByName(String name){
return coffeeMap.get(name);
}
public void add(Coffee coffee){
coffeeMap.put(coffee.getName(), coffee);
}
}
@Service
public class CoffeeService {
private final CoffeeRepository coffeeRepository;
public CoffeeService(CoffeeRepository coffeeRepository){
this.coffeeRepository = coffeeRepository;
}
}
이제 해당 코드를 테스트하는 코드를 작성해보겠습니다. 커피 이름으로 조회를 잘 하는지 테스트하는 코드입니다. @Before에서 테스트전에 coffeeRepository를 만들어서 세팅을해주고 coffeeService객체에 주입을 해줍니다.
public class CoffeeServiceUnitTest{
private CoffeeRepository coffeeRepository;
private CoffeeService coffeeService;
@Before
pulic void setUp(){
coffeeRepository = new CoffeeRepository();
coffeeRepository.add(new Coffee("mocha"));
coffeeService = new CoffeeService(coffeeRepository);
}
@Test
public void getCoffeeTest(){
Coffee coffee = coffeeService.findByName("mocha");
assertEquals("mocha", coffee.getName());
}
}
단위 테스트만 잘 작성하여도 많은 버그를 잡을 수 잇습니다. 하지만 부득이하게 통합테스트를 진행해야하는 상황도 있습니다. 모든 코드를 단위테스트로 수행하겠다는 것은 바람직하지 않습니다. 하지만 스프링 통합 테스트는 너무 느리다는 단점이 있습니다.
5. @SpringBootTest와 @MockBean을 이용한 테스트
@SpringBootTest 어노테이션을 사용하여 통합 테스트를 진행하면서 Mock 객체를 사용해야 하는 경우가 있습니다. 스프링에서는 @MockBean 어노테이션을 제공합니다. "mocha"라는 이름으로 findByName 메소드를 실행하면 "커피모카"라는 이름을 가진 Coffee 객체를 반환해줍니다. 그리고 assertThat을 이용하여 2개의 값이 동일한지 테스트 코드를 작성합니다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class CoffeeServiceUnitTest{
@Autowired
private CoffeeRepository coffeeRepository;
@MockBean
private CoffeeService coffeeService;
@Before
pulic void setUp(){
Coffee coffee = coffeeService.findByName("mocha");
when(coffeeRepository.findByName("mocha").thenReturn(new Coffee("커피모카"));
}
@Test
public void getCoffeeTest(){
Coffee coffee = coffeeService.findByName("mocha");
assertThat("커피모카", coffee.getName());
}
}
@SpringBootTest를 이용할 경우 이 테스트와 전혀 상관없는 Bean을 모두 생성합니다. 그래서 테스트 수행 시간이 너무 오래 걸립니다. 통합 테스트를 수행하면서 특정 클래스만 테스트할 수 있도록 @SpringBootTest에 클래스를 지정해줍니다.
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {
CoffeeService.class,
CoffeeRepository.class
})
public class CoffeeServiceUnitTest{
@Autowired
private CoffeeRepository coffeeRepository;
@MockBean
private CoffeeService coffeeService;
@Before
pulic void setUp(){
Coffee coffee = coffeeService.findByName("mocha");
when(coffeeRepository.findByName("mocha").thenReturn(new Coffee("커피모카"));
}
@Test
public void getCoffeeTest(){
Coffee coffee = coffeeService.findByName("mocha");
assertThat("커피모카", coffee.getName());
}
}
이렇게 설정할 경우 테스트를 수행하면 CoffeeService와 CoffeeRepository 두 개의 Bean만 생성을 합니다. 전체 애플리케이션을 실행하는 방법보다 훨씬 빠르게 가능합니다.
6. 실무에서의 @SpringBooTest
실무에서는 @SpringBootTest 어노테이션을 너무 많이 사용하고 있습니다. 단위 테스트를 해도 되는 상황이 이 애노테이션을 사용하는 것은 바람직하지 않습니다. 통합테스트라면 @SpringBootTest 어노테이션을 사용하고, 그렇지 않다면 @SpringBootTest 어노테이션을 사용하면 안됩니다. 통합테스트 중에서도 특정 클래스만하면 되는 경우에는 @SpringBootTest 어노테이션의 classes 속성에 반드시 클래스를 지정해주도록합니다.
통합테스트와 단위테스트의 비중을 고르는건 매우 어려운일입니다. 보통 통합테스트 50%, 단위테스트 50%로 반반씩 구성을 한다는 의견이 많습니다. 각자의 상황에 맞게 결정해야합니다.
REFERENCE
https://brunch.co.kr/@springboot/207
https://cheese10yun.github.io/spring-boot-test/
'Spring Boot' 카테고리의 다른 글
[Spring Boot] @Valid를 이용한 유효성 검사 (0) | 2020.08.09 |
---|---|
[Spring Boot] Swagger-UI 적용 (0) | 2020.05.31 |
[Spring Boot] 의존성 주입 생성자 주입 (0) | 2020.05.03 |
[Spring Boot] 타임리프(Thymeleaf) 엔진 (0) | 2020.03.04 |
[Spring Boot] 스프링부트 jenkins, docker, github사용하여 배포 (0) | 2020.03.01 |