예제 코드
public class Calculator {
public double sum(double first, double second) {
return first + second;
}
}
테스트 코드(AAA)
@Test
void sumOfTwoNumber() {
//준비
double first = 10;
double second = 20;
Calculator calculator = new Calculator();
//실행
double result = calculator.sum(first, second);
assertThat(result).isEqualTo(30);
}
모든 테스트가 단순하고 균일한 구조를 갖는데 도움이 된다. (일관성)
테스트를 쉽게 읽고, 이해할 수 있다.
구조
- 준비 구절에서는 테스트 대상 시스템과 해당 의존성을 원하는 상태로 만든다.
- 실행 구절에서는 SUT에서 메서드를 호출하고 준비된 의존성을 전달하며 (출력이 있으면) 출력 값을 캡처한다.
- 검증 구절에서는 결과를 검증한다.
- Given: 준비 구절에 해당
- When: 실행 구절에 해당
- Then: 검증 구절에 해당
AAA패턴과 차이점은 없다.
프로그래머가 아닌 사람에겐 Given-When-Then 구조가 더 읽기 쉽다.
테스트를 작성할 때 준비 구절부터 시작 후 실행, 검증 하는 방식이 자연스럽고 대부분의 경우에 효과적이다.
검증 구절로 시작하는 것도 가능하다.
TDD를 실천할 때, 즉 기능을 개발하기 전에 실패할 테스트를 만들 때는 아직 기능이 어떻게 동작할지 충분히 알지 못한다.
따라서 기대하는 동작으로 윤곽을 잡은 후 개발을 한다.
특정 동작이 무엇을 해야 하는지에 대한 목표를 생각하면서 시작하는 것이다.
테스트 준비 -> 실행 -> 검증 -> 더 실행 -> 다시 검증
여러 개의 준비, 실행, 검증 구절은 테스트가 너무 많은 것을 한번에 검증한다는 의미다. 이러한 테스트는 나눠서 해결한다.
여러 개의 동작 단위르 검증하는 테스트는 단위 테스트가 아니라 통합 테스트다.
이러한 구조는 피하자. (리팩토링 하자)
실행이 하나면 테스트가 단위 테스트 범주에 있게끔 보장하고, 간단하고, 빠르며, 이해하기 쉽다.
각 동작을 고유의 테스트로 도출하도록 한다.
통합 테스트의 속도를 높이기 위해 여러 실행과 검증이 있는 단일한 테스트로 묶을 수 있다.
통합 테스트의 경우다.
통합 테스트의 경우에도 속도가 빠르다면, 항상 여러 개의 테스트로 나누는 것이 좋다.
안티 패턴이다.
단위 테스트든 통합 테스트든 분기가 없는 간단한 일련의 단계여야 한다.
if문은 테스트가 한 번에 너무 많은 것을 검증한다는 표시다. (여러개로 나눠야 한다.)
쓰지 말자.
일반적으로 준비 구절이 가장 크다.
크기가 많이 클 경우, 같은 테스트 클래스 내 비공개 메소드 또는 별도의 팩토리 클래스를 활용하자.
실행 구절은 보통 코드 한 줄이다. 두 줄 이상인 경우 SUT의 공개 API에 문제가 있을 수 있다.
단일 작업을 수행하는데 두개의 메소드 호출이 필요하다는 것은 문제가 있다.
테스트 자체는 문제가 되지 않지만 클라이언트에게 메소드 호출을 더 강요해서는 안된다.
그렇게 하지 않으면 클라이언트 코드가 첫 번째 메소드만 호출하고 두 번째 메소드를 호출하지 않을 때 모순이 생긴다. 이러한 모순을 불변 위반 이라고 하고, 잠재적 모순으로부터 코드를 보호하는 행위를 캡슐화 라고 한다.
캡슐화를 지키자, 클라이언트 코드에 의존하지 말자, 불변 위반을 초래할 수 있는 잠재적인 행동을 제거해야 한다.
-비즈니스 로직은 필수지만, 유틸리티나 인프라 코드는 덜 적용된다.
단일 동작 단위는 여러 결과를 낼 수 있으며, 하나의 테스트로 그 모든 결과를 평가하는 것이 좋다.
검증 구절이 너무 커지는 것은 제품 코드에서 추상화가 누락됐을 수 있다.
SUT에서 반환된 객체 내에서 모든 속성을 검증하는 대신 객체 클래스 내애 적절한 동등 멤버랄 정의하는 것이 좋다.
애플리케이션에서 호출하고자 하는 동작에 대한 진입점을 제공한다.
동작은 여러 클래스에 걸쳐 있을 만큼 클 수도, 단일 메소드로 작을 수도 잇다.
그러나 진입점은 오직 하나만 존재할 수 있다.
SUT와 의존성을 구분하는 것이 중요하다.
테스트 대상을 찾는 데 시간이 많이 든다면 테스트 내 SUT이름을 sut로 하자.
@Test
void sumOfTwoNumber() {
//준비
double first = 10;
double second = 20;
Calculator calculator = new Calculator();
//실행
double sut = calculator.sum(first, second); // sut이름 sut
//검증
assertThat(result).isEqualTo(30);
}
주석을 제거하고 빈 줄로 구분하자. 그렇지 않을 경우 주석으로 구분하자.
의존성에서 SUT를 떼어내는 것이 중요하듯이, 테스트 내에서 특정 부분이 어떤 구절에 속해 있는지 파악하는데 시간이 많이 들이지 않도록 세 구절을 서로 구분하는 것이 중요하다.