본문 바로가기
카테고리 없음

테스트주도개발(TDD) 시작하기

by devstep 2024. 12. 22.

테스트 코드의 필요성

복잡한 요구사항들이 나열되어 있는 코드와 부족한 문서의 환경에서 일하면서 아래 고민을 하였다. 

도메인지식을 잘 정리해 놓는다는 것은 무엇일까?

현업에서 복잡한 요구사항을 정리하고 이를 제대로 구현했는지 테스트하는 과정에서 테스트 코드의 중요성을 다시 한번 깨닫게 되었다.
포스트맨에서 여러 케이스에 대해 다양한 파라미터를 설정해 응답을 확인하면서, 테스트코드가 도메인 지식을 정리하는 수단이 되는지 물어본다면, "그렇다"라고 말할 수 있다.

테스트 코드는 이런 상황에서 훌륭한 문서 역할을 할 수 있을까?

테스트 코드가 중요한 내용을 빠뜨리지 않도록 돕는 역할을 한다고 생각한다.
개발 범위를 놓치지 않기 위해 테스트 코드를 작성하면, 그 자체로 비즈니스를 완성하는 데 기여할 수 있다.
도메인 지식을 정리하는 방법으로 ERD나 다이어그램 같은 도구도 있지만, 추가되거나 변경된 요구사항을 즉시 확인할 수 있는 도구는 테스트 코드가 가장 효과적이라고 생각한다.

 

TDD라는 단어에만 몰두하기 보다는 내 코드를 견고하게 하고 빠르게 개발하기 위한 수단으로 TDD를 연습해보고자 한다. 
이번에 포인트 충전, 사용, 조회 API를 구현하는 프로젝트를 진행하며 TDD를 적용했는데, 진행 속도가 더딘 이유와 이를 개선할 방법을 고민하기 위해 최범균님의 테스트주도개발 시작하기라는 책을 읽고 내용을 정리했다.

 


TDD를 몸에 익히기

  • 기존 코드를 수정할 때 습관적으로 테스트를 실행하도록 반복 연습
  • 테스트 실행이 어색하지 않을 때까지 반복

테스트 코드 작성 순서

초기 시행착오

TDD를 처음 시도하면서 초반에 진도가 나가지 않았던 이유는 다음과 같다:

  1. Service 계층부터 테스트 시도
    • 가장 작은 단위부터 시작하는 것이 TDD를 더 쉽게 진행하는 방법이다.
    • 한 번에 구현해야 할 코드 양이 많아지면 TDD가 복잡하게 느껴질 수 있다.

레이어드 아키텍쳐에서 가장 하위 계층인 도메인부터 테스트를 시작하니 이후로는 순조롭게 진행할 수 있었다.

구현 방법

  1. 구현하기 쉬운 것부터 시작
    • 모든 규칙을 충족하는 경우를 먼저 작성한다.
    • 이후 충족하지 않는 조건을 하나씩 추가로 작성.
  2. 예외 상황 고려
    • 예외 상황을 미리 생각하지 않으면 추후 코드 구조를 크게 변경해야 할 수 있다.

구현 단계의 완급 조절

  1. 정해진 값 리턴
  2. 값 비교를 이용해 정해진 값 리턴
  3. 다양한 테스트를 추가하며 구현을 일반화

테스트 작성과 통과 과정을 반복하며 구현이 막힐 때, 위 단계를 차근차근 적용해 문제를 해결할 수 있었다.

리팩토링 시점

  1. 구현 코드 리팩토링
    • 메서드 추출과 같은 리팩토링은 큰 틀에서 구현 흐름이 보이기 시작한 뒤 진행.
  2. 테스트 메서드 리팩토링
    • 중복된 부분을 제거하되, 각 테스트 메서드가 무엇을 테스트하는지 명확히 읽힐 수 있도록 유지.

TDD와 기능 명세, 설계

  • 테스트 코드를 작성하려면 파라미터와 결과값을 정해야 함.
  • 요구사항 문서에 기능 입력결과 도출하는 과정과 같다.
  • 테스트 코드는 "예를 이용한 명세"가 된다.

 

테스트코드 라이프사이클

  • @Test 메서드 실행할 때마다 객체를 새로 생성한다.
  • 각 메서드는 서로 독립적으로 동작한다. 
public class LifecycleTest {

    public LifecycleTest() {
        System.out.println();
        System.out.println("new LifecycleTest");
    }

    @BeforeEach
    public void setUp() {
        System.out.println("setUp");
    }

    @Test
    public void test1() {
        System.out.println("test1");
    }

    @Test
    public void test2() {
        System.out.println("test2");
    }

    @AfterEach
    public void tearDown() {
        System.out.println("tearDown");
    }

}

실행결과 

new LifecycleTest
setUp
test1
tearDown

new LifecycleTest
setUp
test2
tearDown

 

 

댓글