귀찮음..어려움..시간없음..
개발을 하면서 테스트의 중요함에 대해서 많이 들어왔다.
하지만 적용하기란 쉽지않다.
테스트 코드 없이 개발되어 유지되고 있는 커다란 프로젝트에 테스트 코드를 도입하는게 많은 비용이 들기 때문이다.
동료들의 동의와 학습도 필요하고 할 시간이 없다. 기능 개발하기도 바쁜데 수많은 기존 코드들의 테스트 코드까지 짜야 한다고..?
충분한 학습과 의지가 없으면 사실 테스트 코드 자체가 짐덩어리가 되는 경우도 생기기도 한다.
신규 기능을 개발할 때 기존 로직을 수정해야 하거나 공통으로 사용하는 부분을 수정하는 상황이 오면 겁난다. 다른 API들에 영향을 얼마나 미칠지 모르기 때문이다.
테스트 코드가 없을 때의 리팩토링은 정말 큰 작업이다. 관련 된 부분의 전체 테스트가 필요하다.
회기 버그가 발생하기도 하고 기존에 테스트 했던 영역을 또 테스트를 해야할 때도 있다. 그래서 서비스 커질 수록 QA기간도 늘어나고 배포가 불안하다.
개발하다보면 사실 기능 개발보다 디버깅에 더 많은 시간이 소요된다.
테스트 코드가 없으면 디버깅 시간이 늘어난다. -> 개발에 집중할 수 있는 시간이 줄어든다. -> 실수 확률이 올라간다. -> 개발 시간이 더 소요된다 -> 테스트 코드 짤 시간이 부족해진다. -> ...
악순환이다.
시도하지 않으면 변하는 것은 없다!
그러다 개인적으로라도 테스트 코드를 짜보자는 생각이 들었다.
테스트 코드가 없는 프로젝트라면 레이어드 아키텍처로 구성되어 있을 확률이 높다.
테스트 코드가 익숙하지 않으니 어떻게 시작해볼까 고민을 하다가 비즈니스 레이어의 통합테스트로 시작해 보기로 했다.
기존 개발 프로세스를 보자면
처음엔 괜찮았지만 서비스가 커지고 API가 많아지면 로컬에서 서버 돌리는데도 시간이 걸리고 몇 번 재실행 하다보면 답답하다. 서버 실행 완료 될 때까지 기다렸다가 API호출 해야 하니 귀찮다.
그래서 첫 시작은 내가 짠 로직을 요구사항대로 잘 동작하는 지에 대한 자동 검증을 위한 테스트 코드가 아니고 내가 짠 로직을 실행해보는 것에 목적을 둔 테스트 코드로서 사용했다.
예를 들면 이런식으로
@SpringBootTest
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Test
@DisplayName("주문을 생성한다.")
void createOrder() {
// 요청 파라미터
OrderCreateRequest request = createOrderRequest();
// 서비스 메소드 실행
OrderResponse result = orderService.createOrder(request);
// 응답 출력
System.out.println("result = " + result);
}
}
개발DB 환경에서 테스트 코드 실행 후 응답 값을 직접 눈으로 보고 버그를 찾고 수정하고 다시 테스트코드 실행을 반복하면서 개발했다.
테스트 코드로서의 역할은 전혀 하지 못했지만 이렇게만 하더라도 서버실행과 API 호출을 동시에 할 수 있었기 때문에 생산성 향상을 느낄 수 있었다.
이렇게 테스트 코드를 짜는 것을 시작해 테스트환경을 분리하고(H2 메모리DB) 검증 로직을 추가해 통합테스트를 작성했다.
간단하게 예를 들면
@SpringBootTest
class OrderServiceTest {
@Autowired
private ProductRepository productRepository;
@Autowired
private OrderService orderService;
...
@Test
@DisplayName("주문을 생성한다.")
void createOrder() {
// given
Product product1 = createProduct();
Product product2 = createProduct();
Product product3 = createProduct();
productRepository.saveAll(List.of(product1, product2, product3));
OrderCreateRequest request = createOrderRequest();
...
// when
OrderResponse result = orderService.createOrder(request);
// then
assertThat(result.getId()).isNotNull();
assertThat(result.getTotalPrice()).isEqualTo(5000);
...
}
}
통합테스트만 작성하더라도 만족스러웠다.
단위테스트를 추가하기 시작하면 고민이 많아진다.
주문 같은 핵심 도메인에 대한 중요한 로직먼저 단위테스트를 추가해본다.
테스트 코드 작성이 어렵고 귀찮아 지면서 아키텍쳐를 개선하려고 생각하게 된다.
객체 간의 의존성을 약하게 만들기도 하고(인터페이스), 쓸데없는 의존성 주입을 줄이기도 하고 상위 계층을 의존하고 있는 부분들을 찾아내기도 한다.
결국 테스트를 쉽게 하기 위해 고민 하다보면 좋은 방향으로 가게 되더라.
"한 번 시작하고나면 presentation계층, persistence계층 테스트에 손을 대고 있는 자신을 발견할 것이다. 그러다 DDD, 아키텍처에 관심을 가지게 될 것이다."
이렇게 하나 둘씩 테스트가 많아지면 많아 질 수록 의외의(?) 좋은점이 있다.
해당 도메인에 대한 정책 등 정보가 부족하면 다른사람이 짠 코드 보기 쉽지않다. 그런데 테스트 코드가 잘 짜여저 있다면 테스트 코드만 봐도 이해하기 쉬워진다.
예를 들면
@Test
@DisplayName("로그인 시 5번 이상 실패하면 로그인 제한 상태가 된다.")
void login_fail() {
...
}
이 테스트 코드를 보면 로그인 제한 정책을 쉽게 알 수 있다.
@Test
@DisplayName("재고가 부족한 상품을 주문 할 경우 예외가 발생한다.")
void createOrder_fail() {
...
}
이 테스트코드를 보면 어떤 상황에서 어떤 예외가 발생하는지 쉽게 알 수 있다.
잘 짜여진 테스트 코드를 읽는 것으로 어떤 상황에 어떤 흐름으로 어떤 동작을 하고 어떤 결과가 나오는지 이해하는데 도움이 된다.
테스트 하면 TDD를 빼놓을 수 없다.
익숙해 진다면 신뢰성있는 소프트웨어를 개발하는데 도움이 될..지도?
테스트 코드 시작하자!