[테스트 주도 개발 시작하기] CHAPTER 4 - TDD ▪ 기능 명세 ▪ 설계

myeonji·2023년 1월 5일
0
  • 기능 명세와 TDD
  • TDD와 설계 행위
  • 기능 명세 구체화

기능 명세

기능에 대한 명세는 다양한 형태로 존재한다.
어떤 형태가 되든 간에 사용자에게 제공할 기능을 구현하려면 기능을 크게 두 가지로 나누어 생각해 볼 수 있다.

예를 들어 로그인 기능을 생각해보면 아래와 같다.

  • 입력 : 아이디와 암호
  • 결과 : 아이디와 암호가 일치하면 성공, 일치하지 않으면 실패

설계는 기능 명세로부터 시작한다.
스토리보드를 포함한 다양한 형태의 요구사항 문서를 이용해서 기능 명세를 구체화한다. 기능 명세를 구체화하는 동안 입력과 결과를 도출하고 이렇게 도출한 기능 명세를 코드에 반영한다. 기능 명세의 입력과 결과를 코드에 반영하는 과정에서 기능의 이름, 파라미터, 리턴 타입 등이 결정된다.

설계 과정을 지원하는 TDD

TDD는 테스트를 만드는 것부터 시작한다.
테스트 코드를 먼저 만들고 테스트를 통과시키기 위한 코드를 구현하고 리팩토링 하는 과정을 반복한다.

테스트 코드를 먼저 만들기 위해 필요한 것은 다음과 같다.

  • 테스트할 기능을 실행
  • 실행 결과를 검증

테스트는 기능을 실행한 결과를 검증하는 것이므로 테스트 코드에서 기능을 실행할 수 있어야 한다.
즉 테스트에서 실행할 수 있는 객체나 함수가 존재해야 한다.

실행할 객체가 존재하려면 객체를 생성할 때 사용할 클래스가 필요하고 실행할 메서드도 필요하다. 테스트 대상이 되는 클래스와 메서드의 이름을 결정해야 하고, 메서드를 실행할 때 사용할 인자의 타입과 개수를 결정해야 한다.

지난 게시글에서 다룬 암호 강도 측정과 만료일 계산 기능도 유사한 과정을 거쳤다.

  • 클래스 이름
  • 메서드 이름
  • 메서드 파라미터
  • 실행 결과

이 네 가지를 결정하는 과정에서 이름을 고민하고 파라미터 타입과 리턴 타입을 고민했다.
TDD에서 테스트 코드를 작성할 때 고민하는 것과 설계 과정에서 고민하는 것은 겹치는 부분이 있음을 알 수 있다.
즉 TDD 자체가 설계는 아니지만, TDD를 하다 보면 테스트 코드를 작성하는 과정에서 일부 설계를 진행하게 된다.

이름은 설계에서 매우 중요하다. 테스트 코드는 시작부터 이름을 고민하게 만든다. 설계 과정에서 구현하는 기능을 정확하게 표현하는 이름을 사용한 것만큼 중요한 것은 없다.

필요한 만큼 설계하기

TDD는 테스트를 통과할 만큼만 코드를 작성한다.
실제 테스트 사례를 추가하고 통과시키는 과정에서 필요한 만큼 설계를 변경한다.

앞에서 했던 만료일 계산 예를 보았다.

// 최초 설계는 두 개의 파라미터를 사용
LocalDate expiryDate = cal.calculateExpiryDate(billingDate, payAmount);

테스트 사례를 추가하는 과정에서 만료일을 계산하는데 첫 납부일을 추가 파라미터로 사용하게 되었다.
이를 반영하기 위해 납부일, 납부액, 첫 납부일을 담는 타입을 새로 만들었다.

// 테스트를 진행하는 과정에서 필요한 만큼 설계 변경
PayData payData = PayData.builder()
	.firstBillingDate(LocalDate.of(2019, 1, 31))
    .billingDate(LocalDate.of(2019, 2, 28))
    .payAmount(10_000)
    .build();
LocalDate expiryDate = cal.calculateExpiryDate(payData);

필요할 것으로 예측해서 미리 코드를 만들지 않는다.
필요할 것으로 예측해서 미리 설계를 유연하게 만들지 않는다.

기능 실행 결과도 동일하다.
미리 앞서서 필요해 보이는 익셉션 타입을 만들지 않는다.
테스트를 진행하는 과정에서 실제 익셉션이 필요한 시점에 익셉션을 도출한다.

TDD로 개발을 진행하면 현시점에서 테스트를 통과시키는데 필요한 만큼의 코드만 만들게 된다. 유연한 설계는 필요한 시점에 추가한다.
필요한 만큼 설계를 한다고 해서 사전에 설계 활동을 생략하는 것은 아니다. 요구사항을 분석하는 과정에서 당연히 설계를 진행한다. 단 이때 설계한 결과물은 초안에 불과하다. 최초 요구사항은 시간이 지나면 변하기 때문에 TDD는 미리 앞서서 코드를 만들지 않으므로 불필요한 구성 요소를 덜 만들게 된다.

기능 명세 구체화

테스트 코드를 작성하기 위해 개발자는 기능 명세를 정리해야 한다.
파라미터와 결과 값을 정해야 하므로 개발자는 요구사항 문서에서 기능의 입력과 결과를 도출해야 한다.

만료일 계산 기능을 예로 들었다.

  • 서비스를 사용하려면 매달 1만 원을 선불로 납부한다. 납부일 기준으로 한 달 뒤가 서비스 만료일이 된다.
  • 2개월 이상 요금을 납부할 수 있다.
  • 10만 원을 납부하면 서비스를 1년 제공한다.

하지만 개발자는 한 달 뒤에 대한 정확한 예가 필요하다.

  • 1월 31일에 만 원을 납부하면 만료일은 2월 28일인가? 30일 뒤인 3월 2일인가?
  • 윤년인 경우는 어떻게 되나?
  • 1월 31일에 만 원을 납부하고 만료일인 2월 28일에 다시 2만 원을 납부하면 그 사용자의 만료일은 4월 28일인가? 4월 30일인가?

등등 모호한 상황을 만나면 이를 기획자와 얘기한 뒤 구체적인 예로 바꾸어 테스트 코드에 반영한다.
테스트 코드는 예를 이용한 구체적인 명세가 된다.

구체적인 예를 이용해서 테스트 코드를 추가하다 보면 기능 명세를 보다 잘 이해하고 모호함을 없앨 수 있다. 테스트 코드는 바로 실행할 수 있으므로 구체적인 예를 이용해서 기능을 바로 실행해 볼 수 있다.

0개의 댓글