Notice
suyeonme
[테스트] 좋은 단위 테스트(Unit Test) 작성하기 본문
단위 테스트(Unit Test)
단위 테스트는 단일 클래스나 메서드처럼 범위가 상대적으로 좁은 테스트를 뜻한다.
단위 테스트 장점
- 단위 테스트는 빠르고 결정적(deterministic)이여서 개발자들이 수시로 수행하며 피드백을 즉각 얻을 수 있다.
- 대상 코드와 동시에 작성할 수 있을만큼 작성하기 쉽다.
- 빠르게 작성할 수 있으므로 테스트 커버리지를 높이기 좋다.
- 테스트는 개념적으로 간단하고 시스템의 특정 부분에 집중하므로 실패시 원인 파악이 쉽다.
- 대상 시스템의 사용법과 의도한 동작 방식을 알려주는 문서자료 역할을 한다.
단위 테스트를 수행해야하는 시점
- 순수 리팩터링
- 새로운 기능 추가
- 버그 수정
- 코드의 행위 변경
좋은 단위 테스트를 작성하는 원칙
공개 API를 이용해 테스트하자
- 내부 구현을 위한 코드가 아닌 공개 API를 호출해서 테스트를 한다.
- 예를 들어, 똑같이 public으로 지정된 메서드라도 다른 공개 API를 구현하면서 생긴 파생 메서드일 수 있다. 이런 메서드는 내부구현에 해당하므로 직접 테스트하지 않고 다른 공개 API를 테스트하는 과정에서 간접적으로 테스트한다.
상호작용이 아니라 상태를 테스트하자
상태 테스트(state test)
- 메소드 호출 후 시스템 자체를 관찰하는 테스트
상호작용 테스트(interaction test)
- 호출을 처리하는 과정에서 시스템이 다른 시스템과 협력하여 기대한 일련의 동작을 수행하는지 확인하는 테스트
- 무엇(what)이 아닌 어떻게(how) 작동하는지를 확인하기 때문에 상태 테스트보다 깨지기 쉽다.
행위 주도 테스트로 테스트를 완전하고 간결하게 만들자
- 완전한 테스트(complete test): 결과에 도달하기까지의 논리를 읽는 이가 이해하는데 필요한 모든 정보를 본문에 담고있는 테스트
- 간결한 테스트(concise test): 코드가 산만하지 않고 관련 없는 정보는 포함하지 않은 테스트
테스트를 메서드별로 작성하지 말고 행위(behavior)별로 작성해야한다. 즉 메서드 중심이 아닌 행위 주도 테스트를 작성한다.
// 메서드 중심 테스트
@Test
public void testDisplayTransactionResults() {
transactionProcessor.displayTransactionResult(
newUserWithBalance(LOW_BALANCE_THRESOLD.plus(dollars(2))),
new Transaction("물품", dollars(3))
);
assertThat(ui.getText()).contains("물품 구입");
assertThat(ui.getText()).contains("잔고 부족");
}
// 행위 중심 테스트
@Test
public void displayTransactionResults_showItemName() {
transactionProcessor.displayTransactionResult(new User(), new Transaction("물품"));
assertThat(ui.getText()).contains("물품 구입");
}
@Test
public void displayTransactionResults_showLowBalanceWarning() {
transactionProcessor.displayTransactionResult(
newUserWithBalance(LOW_BALANCE_THRESOLD.plus(dollars(2))),
new Transaction("물품", dollars(3))
);
assertThat(ui.getText()).contains("잔고 부족");
}
행위 주도 테스트 행위를 때로는 given/when/then을 써서 표현하기도 한다. 이 문법을 지원하는 프레임워크로 Cucumber, Spock이 있다. 테스트 이름은 should를 사용해도 좋다.(e.g. shouldNotAllowWithdrawalsWhenBalanceIsEmpty)
- given: 시스템의 설정을 정의
- when: 시스템이 수행할 작업
- then: 결과를 검증
- and: 긴 블록을 쪼갠 후, and으로 연결한다.
@Test
public void transferFundsShouldMoveMoneyBetweenAccounts() {
// Given: 두개의 계좌, 각각의 잔고는 $150, $20
Account account1 = newAccountWithBalance(usd(150));
Account account2 = newAccountWithBalance(usd(20));
// When: 첫번째 계좌에서 두번째 계좌로 $100 이체
bank.transferFunds(account1, account2, usd(100));
// Then: 각 계좌 잔고에 이체 결과가 반영됨
assertThat(account1.getBalance()).isEqualTo(usd(50));
assertThat(account2.getBalance()).isEqualTo(usd(120));
}
테스트에는 논리를 넣지 말자
테스트 코드에는 스마트한 로직보다 직설적인 코드를 고집해야한다. 더 서술적이고 의미있는 테스트코드를 만들기 위한 약간의 정복은 허용해도 좋다. 테스트 코드에는 DRY가 아니라 DAMP(Descriptive And Meaningful Phrase)가 되도록 노력해야한다.
// 논리가 버그를 감추는 코드
@Test
public void shouldNavigateToAlbumPage() {
String baseUrl = "http://phtos.google.com/";
Navigator nav = new Navigator(baseUrl);
nav.goToAlbumPage();
assertThat(nav.getCurrentUrl()).isEqualTo(baseUrl + "/albums"); // BUG! 알아차리기 어려움
}
// 논리를 제거하니 버그가 드러남
@Test
public void shouldNavigateToAlbumPage() {
Navigator nav = new Navigator("http://phtos.google.com/");
nav.goToAlbumPage();
assertThat(nav.getCurrentUrl()).isEqualTo("http://phtos.google.com//albums"); // BUG!
}
'프로그래밍👩🏻💻 > 기타' 카테고리의 다른 글
[Nestjs, Axios] Access Token과 Refresh Token으로 인증 구현하기(클라이언트, 서버) (0) | 2024.06.18 |
---|---|
[Fix] 모노레포 환경의 Vscode에서 Eslint가 동작하지않는 현상 (0) | 2024.05.05 |
JSON Web Token(JWT)이란? (1) | 2023.08.06 |
[MSA] 마이크로서비스(Mocroservice)란? (1) | 2022.12.31 |
[의존성 관리] Semantic Versioning이란? (0) | 2022.11.20 |
Comments