ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TDD (Test-Driven Development)
    Backend/테스트 코드 2024. 9. 27. 20:20

    TDD란?

    프로덕션 코드를 먼저 작성하는 것이 아닌 테스트 코드를 먼저 작성한다.

     

    TDD, 참고 : https://www.icterra.com/tdd-is-not-about-testing-but-the-design/

     

    • RED : 테스트에 실패하는 코드를 작성한다.
      • 테스트할 프로덕션 코드가 없기 때문에 에러가 발생한다.
    • GREEN : 테스트를 통과할 수 있도록 최소한의 코드만 작성한다.
      • 프로덕션 코드가 엉터리여도 된다. 단순히 테스트 통과만을 위한 프로덕션 코드를 작성한다.
    • BLUE : 테스트 통과는 유지하면서 프로덕션 코드를 개선한다.

     

    TDD - RED

    calculateTotalPrice()는 프로덕션에서 실제로 사용할 코드에 대한 테스트 코드를 작성한다. CafeKiosk 클래스의 calculateTotalPrice() 메서드는 아직 작성되지 않은 상태이다.

    @Test
    void calculateTotalPrice() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        Latte latte = new Latte();
    
        cafeKiosk.add(americano);
        cafeKiosk.add(latte);
    
        int totalPrice = cafeKiosk.calculateTotalPrice();
    
        assertThat(totalPrice).isEqualTo(8500);
    }

     

    프로덕션 코드calculateTotalPrice()가 존재하지 않기 때문에 컴파일 에러가 발생한다.

    컴파일 에러 발생

     

    컴파일 에러가 발생하는 이유는 프로덕션 코드에 calculateTotalPrice()가 없기 때문이다. 따라서 CafeKios 클래스에 calculateTotalPrice() 메서드를 추가한다.

    @Getter
    public class CafeKiosk {
    	
    	public int calculateTotalPrice() {
    		return 0;
    	}
    	
    }

     

    TDD - GREEN

    컴파일 에러를 해결하고 테스트 코드를 실행하면 아래 사진과 같이 테스트에 실패한다. 왜냐하면 RED 영역에서 단순히 테스트를 실행 가능한 상태로만 작성했기 때문이다.

    테스트 실패

     

    GREEN에서는 테스트 통과만을 위한용도로 프로덕션 코드를 수정한다. 테스트가 실패하는 이유는 현재 calculateTotalPrice()0을 반환하고 있기 때문이다. 테스트 코드를 성공하기 위해서는 8500을 반환해야 하기 때문에 calculateTotalPrice()8500을 반환하도록 수정한다.

    @Getter
    public class CafeKiosk {
    	
    	public int calculateTotalPrice() {
    		return 8500; // 0 -> 8500
    	}
    	
    }

     

    테스트 코드의 통과만을 위해서 프로덕션 코드를 0 → 8500으로 수정했다. 그 결과 테스트 코드는 성공하게 된다.

    테스트 통과

     

    TDD - BLUE

    BLUE에서는 실제 프로덕션 코드를 작성하되, 테스트 코드는 당연히 통과해야 한다.

    기능 요구사항으로 calculateTotalPrice()는 주문 목록에 담긴 음료들의 가격 합을 반환해야 한다. 따라서 beverages에 담긴 음료들의 가격들을 더한 값을 반환한다.

    @Getter
    public class CafeKiosk {
    
    	public int calculateTotalPrice() {
    	    int totalPrice = 0;
    	    for (Beverage beverage : beverages) {
    	        totalPrice += beverage.getPrice();
    	    }
    	    return totalPrice;
    	}
    }

     

    BLUE 영역에서는(TDD에서는) 과감하게 리팩토링도 가능하다. 왜냐하면 테스트 코드가 프로덕션 코드의 안전성을 보장하기 때문에, 리팩토링한 코드가 테스트에 성공한다면 문제가 없음을 의미한다.

    @Getter
    public class CafeKiosk {
    
    	public int calculateTotalPrice() {
    	    return beverages.stream()
    	        .mapToInt(beverage -> beverage.getPrice())
    	        .sum();
    	}
    	
    }

     

    선 기능 구현, 후 테스트 작성

    • 개발 기간이 부족할 경우, 테스트를 작성할 시간이 없어 테스트 코드를 누락할 수 있다.
    • 해피 케이스(성공하는 케이스)만 검증할 수 있다.
      • 프로덕션 코드는 해피 케이스만 고려하며 작성하기 때문에, 이를 검증할 때는 예외 케이스를 고려하지 못할 수 있다.
    • 잘못된 구현부를 나중에 확인하게 된다.

     

    선 테스트 작성, 후 기능 구현 (TDD 방식)

    • 테스트 코드를 작성하면서 해피 케이스예외 케이스를 고려하면서 작성할 수 있다.
      • 프로덕션 코드는 두 케이스를 모두 통과하기 위한 코드를 작성하게 된다.
    • 프로덕션 코드에 대한 빠른 피드백이 가능하다.
      • 테스트 코드를 먼저 작성하고 프로덕션 코드를 작성하기 때문에, 프로덕션 코드에 대한 빠른 피드백이 가능하다. → 피드백 : 작성한 프로덕션 코드가 테스트에서 실패한다면 바로바로 수정할 수 있다.
    • 테스트 코드를 작성하면서 프로덕션 코드의 복잡도(유지 보수)를 낮출 수 있다.
      • 프로덕션 코드를 먼저 작성하고 테스트 코드를 작성할 때, 테스트 코드를 작성하는데 어려움이 생긴다면 프로덕션 코드와 테스트 코드 둘 다 다시 작성해야 하는 문제가 발생할 수 있다.
    • 프로덕션 코드의 과감한 리팩토링이 가능하다.
      • 테스트 코드를 성공한다는 것은 프로덕션 코드에 문제가 없다는 것이다. 따라서 기존의 프로덕션 코드를 리팩토링 하였을 때, 테스트에 통과한다면 프로덕션 코드에 문제가 없음을 보장하게 된다.

    프로덕션 코드를 먼저 작성하고 테스트 코드를 작성하게 되면, 테스트 코드를 작성할 때 프로덕션 코드에 맞춰서 테스트 코드를 작성해야 한다. 이는 프로덕션 코드를 위한 테스트 코드를 작성하게 되기 때문에 해피 케이스만 고려하게 된다. 반대로 테스트 코드를 먼저 작성하면 프로덕션 코드에서는 테스트 통과를 위한 코드를 작성하게 된다. 이는 코드의 안전성을 보장하고 예외 케이스도 고려할 수 있게 된다.

     

    TDD에 대한 내 생각

    난 지금까지 프로덕션 코드를 먼저 작성하고 테스트 코드를 작성하였다. 프로덕션 코드를 작성할 때는 예외 케이스를 고려하지 않고 작성하기 때문에 테스트 코드에서도 예외 케이스를 잘 다루지 않았던 것 같다.

     

    프로덕션 코드에 맞춰서 테스트 코드를 작성하게 되다보니, "과연 내가 작성한 테스트 코드가 올바르게 작성된 것인가?" 에 대한 의문이 있었다. 테스트 코드를 작성할 때마다 의문이 계속 커지다 보니 테스트 코드를 작성하기 싫어지는 상황까지 도달하였다.

     

    돌이켜보면 지난 프로젝트에서 테스트에 통과한 프로덕션 코드를 운영 환경에 배포하였을 때 오류가 발생했던 적이 있다. 테스트 코드를 통과했기 때문에 문제가 없었겠거니하고 운영 환경에 올렸는데, 조회 데이터 일부가 사용자 화면에서 누락되는 문제가 발생하였다. 테스트 코드가 불안한 이유를 오늘에서야 알았는데, 그 이유는 프로덕션 코드에 맞춘 테스트 코드를 작성했기 때문이다.

     

    이번 기회로 TDD의 중요성을 실감하면서 앞으로 테스트 코드를 먼저 작성하는 습관을 길러야겠다는 경각심을 가졌다. 테스트 코드를 먼저 작성하는 것이 상당히 낯설지만, 반복적인 학습을 통해 습관으로 만들다 보면 익숙해질 것이라 생각한다. 서비스의 안전성을 위해서라도, 추후에 리팩토링을 위해서라도 프로덕션 코드의 안전성을 위하여 TDD 연습을 해야겠다.

Designed by Tistory.