Post

TDD와 Test 작성 스타일

TDD(Test-Driven Development)


테스트 주도 개발
프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론

간단하게 TDD의 순서를 표현하자면,

  1. 실패한 테스트 코드를 짠다.
  2. 어떻게든 성공시킨다.
  3. 코드를 개선시킨다.

이 사이클을 반복하는 것이 바로 테스트 주도 개발, TDD이다.


극단적으로 아주 쉬운 예시를 들어보자.

1. 실패한 테스트 코드를 짠다.

인자를 2개 받아서, 더해주는 메서드를 TDD로 개발해보자

1
2
3
4
5
6
// 테스트 코드 먼저 작성
@Test
void plus() {
	int result = plus(3, 4);
	assertThat(result).isEqualTo(7);
}
1
2
3
4
// 프로덕션 코드
private int plus(int a, int b) {
	return 0;
}

테스트를 돌려보면, 빨간색이 나오면서 실패할 것이다.
이것이 TDD의 첫 번째 과정이다. plus라는 메서드를 구현하기 이전에, 테스트를 먼저 작성했다는 것이 의미가 있는 것이다.


2. 어떻게든 성공시킨다.

그럼 이제 plus라는 메서드를 수정하여 어떻게든 성공시킨다.

1
2
3
private int plus(int a, int b) {
	return 7;
}

아주 간단하게 리턴값을 7로 바꾸면 해당 테스트는 성공하게된다.
너무 극단적이긴 하지만 테스트를 성공시키기만 하는 과정이 두 번째 과정이다.


3. 코드를 개선시킨다.

이제 이 초록색 불을 유지한 채로 코드를 구현해나간다.

1
2
3
4
private int plus(int a, int b) {
	int result = a + b;
	return result;
}

이렇게 테스트 코드의 주도 하에 개발하는 것이 바로 테스트 주도 개발(TDD) 이다.


Test 작성 스타일

1. Displayname은 최대한 자세하게!

잘못된 예시 1

1
2
3
4
5
6
7
8
@DisplayName("음료 1개 추가 테스트") // ~~ 테스트 지양하기!
@Test
void add() {
	CafeKiosk cafeKiosk = new CafeKiosk();
	cafeKiosk.add(new Americano());

	assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노");
}

음료 1개를 추가하는 add라는 메서드를 테스트할 때, 다음과 같이 명사만 나열하면 정확한 상태 변화를 알기 힘들다.


잘못된 예시 2

1
2
3
4
5
6
7
8
@DisplayName("음료 1개를 추가할 수 있다") // 명사의 나열보다 문장을 작성한다.  
@Test
void add() {
	CafeKiosk cafeKiosk = new CafeKiosk();
	cafeKiosk.add(new Americano());

	assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노");
}

문장으로 작성했다고 해서, 테스트 내용을 전부 명확하게 전달할 수 있는 것은 아니다.
“~~하면 ~~된다”의 방식으로 테스트 행위에 대한 결과까지 자세히 기술하는 것이 좋다.


좋은 예시 1

1
2
3
4
5
6
7
8
@DisplayName("음료 1개를 추가하면 주문 목록에 담긴다.") // 테스트 행위에 대한 결과까지 기술하기
@Test
void add() {
	CafeKiosk cafeKiosk = new CafeKiosk();
	cafeKiosk.add(new Americano());

	assertThat(cafeKiosk.getBeverages().get(0).getName()).isEqualTo("아메리카노");
}


잘못된 예시 3

1
2
3
4
5
6
7
8
9
10
11
@DisplayName("특정 시간 이전에 주문을 생성하면 실패한다.")
@Test
void createOrderOutsideOpenTime() {
	CafeKiosk cafeKiosk = new CafeKiosk();
	Americano americano = new Americano();
	cafeKiosk.add(americano);

	assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 1, 17, 9, 59)))
			.isInstanceOf(IllegalArgumentException.class)
			.hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}

이 Name에는 개선해야할 점이 두 가지가 있다.

  1. 도메인 용어를 사용하여 추상화된 내용을 담기 -> 정책 중심으로..
  2. “성공한다”, “실패한다” 등의 테스트의 현상을 중점으로 기술하지 말 것.


좋은 예시 2

1
2
3
4
5
6
7
8
9
10
11
@DisplayName("영업 시작 시간 전에 주문을 생성할 수 없다.")
@Test
void createOrderOutsideOpenTime() {
	CafeKiosk cafeKiosk = new CafeKiosk();
	Americano americano = new Americano();
	cafeKiosk.add(americano);

	assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 1, 17, 9, 59)))
			.isInstanceOf(IllegalArgumentException.class)
			.hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");
}

“영업 시작 시간”이라는 정책적인 용어를 사용했고, “성공”, “실패”와 같은 테스트 용어를 사용하지 않았다.


BDD(Behavior Driven Development)

TDD에서 파생된 개발 방법
Given, When, Then의 순서에 맞게 코드를 설계하고, 개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준을 가지도록 개발한다.

일반적인 BDD의 구조는 다음과 같다.

  1. Given : 시나리오 진행에 필요한 모든 준비 과정 (객체 생성, 값 생성 등등)
  2. When : 시나리오 행동 진행
  3. Then : 시나리오 진행에 대한 결과 명시, 검증


이 세 과정을 요약하자면,

  • 어떤 환경에서(Given)
  • 어떤 행동을 진행했을 때(When),
  • 어떤 상태 변화가 일어난다(Then)


BDD의 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@DisplayName("주문 목록에 담긴 상품들의 총 금액을 계산할 수 있다.")
@Test
void calculateTotalPrice() {
	// given -> 총 금액 계산에 필요한 객체들을 생성하고, 주문 목록에 알맞게 담는다.
	CafeKiosk cafeKiosk = new CafeKiosk();
	Americano americano = new Americano();
	Latte latte = new Latte();

	cafeKiosk.add(americano);
	cafeKiosk.add(latte);

	// when -> 계산 메서드 수행
	int totalPrice = cafeKiosk.calculateTotalPrice();

	// then -> 금액이 맞는지 검증
	assertThat(totalPrice).isEqualTo(8500);
}

이 포맷으로 코드를 작성하면, DisPlayName도 명확하게 작성할 수 있다.

This post is licensed under CC BY 4.0 by the author.