정글에서 온 개발자
12/28 TIL 단위 테스트의 구조 본문
단위 테스트의 구조
테스트는 클래스나 메서드가 아닌 '동작'을 테스트한다.!
AAA 패턴 (Arrange, Act, Assert; 준비, 실행, 검증)
준비구절
- 일반적으로 가장 크다 (실행과 검증을 합친만큼 클수도 있다.)
- 너무 크다면 오브젝트 마더, 테스트 데이터 빌더를 사용
실행구절
- 검증으로 구분된 실행은 하나만 있어야 한다. - 여러개 있다면 통합 테스트다.
- 실행 구절은 보통 한 줄이다. - 두 줄 이상인 경우 SUT의 공개 API에 문제가 있는 것
- 불변 위반의 모순이 있을 수 있다.
- 캡슐화 - 잠재적 모순으로부터 코드를 보호하는 행위
- 인프라나 유틸리티 코드에서는 두 줄 이상일 수 있다.
검증문
- 테스트의 목표 단위인 '동작'을 검증하기 때문에 여러개일 수 있다.
- 너무 커진다면 제품 코드에서 추상화가 누락됐을 수 있다.
- 객체의 모든 속성을 검증하는 대신, 동등비교 연산자 등을 정의 할 수 있다.
종료단계
- 데이터베이스 정리 등을 넣기도 하지만, 올바른 테스트는 프로세스 외부에 종속적이지 않으므로 처리할 부작용이 없다.
- 종료코드 역시 통합테스트의 영역이다.
기타
- 테스트는 if문이 없어야 한다. 분기가 있다는 건 테스트가 한 번에 너무 많은 것을 검증한다는 표시다. (안티패턴)
- 통합테스트 역시 분기가 없어야 한다.
- SUT는 동작에 대한 진입점을 제공해서 중요하다.
- 동작은 여러 클래스에 걸쳐 있을 수 있지만, 진입점은 오직 하나다.
- SUT를 준비를 위한 다른 의존성과 구분하기 위해 sut 라고 이름지어보자
픽스쳐
픽스처 : 테스트 실행 대상 객체. 실행 전에 고정 상태로 유지해 동일한 결과를 생성한다.
이 픽스처를 전체 테스트 클래스의 생성자로 만들면, 하위 테스트 간 결합도가 너무 높고 첫 생성 코드가 없어 가독성이 떨어진다.
비공개 팩토리 메소드를 통해 이를 해결하자.
public class CustomerTests
{
public CustomerTests()
{
_store = new Store();
_store.AddInventory(Product.Shampoo,10);
_sut = new Customer();
}
[Fact]
public void Purchase_succeeds_when_enough_inventory()
{
//생성자가 생략돼 가독성이 떨어짐.
boo success = _sut.Purchase(_store, Product.Shampoo,10);
...
}
[Fact]
public void 다른 테스트()
{
//생성자가 공통적이라 결합도가 올라감
}
}
위 코드 대신 아래처럼 쓸 수 있다.
public class CustomerTests
{
[Fact]
public void Purchase_suceeds_when_enough_inventory()
{
Store store = CreateStoreWithInventory(Product.Shampoo, 10);
Customer sut = CreateCustomer();
bool success = sute.Purchase(Store, Product.Shampoo,5);
}
[Fact]
public void 다른테스트()
{
//목적에 따라 다른 값으로 store를 세팅할 수 있다.
Store store = CreateStoreWithInventory(Product.Shampoo, 5);
...
}
}
데이터베이스 준비 같은 경우 연결 초기화는 공통 메서드로 써도 된다.
명명법
- 엄격한 명명 규칙을 따르지 말고 자유롭게 이름짓자
- 밑줄은 긴 이름에서 가독성을 향상시킨다.
- 테스트 이름에 SUT 메서드의 이름을 포함하지 말자
- 동작 대신 코드를 목표로 하게 돼서 세부사항과 테스트의 결합도가 높아진다.
- 유틸리티 코드는 예외다. 비즈니스 로직이 없기 때문이다.
- consider라는 단어 들어갈 필요 없다.
- 테스트는 사실이기 때문에 should be가 필요 없다.
- assert가 아닌 테스트 명 이야기다. assert에서는 Equal 보다 should.be 가 가독성에 도움된다.(fluent assertion 라이브러리)
매개변수 테스트
테스트하려는 케이스만 뽑아서 매개변수로 넘겨 여러 테스트를 한 테스트코드로 검사할 수 있다.
이렇게 코드를 줄일 수 있다. 그러나 코드를 줄이는 건 가독성과 트레이드 오프가 된다.
최소한 긍정 테스트 케이스와, 부정 테스트 케이스는 나눠서 작성하는 것이 좋다.
동작이 너무 복잡하다면 매개변수화 하지 말자.
느낀점
책을 읽을면서, 내가 잘하고 있는지 걱정하면서 했던 일들중 권장되는 항목들이 있는 걸 발견하면서 안심되는 게 있다.
- 준비 코드가 길어지는 게 자연스러운 것이라는 것, 그리고 이렇게 길어지는 코드를 다른 코드로 빼는 것도 권장된다는 걸 알게됐다.
- 통합테스트는 유닛테스트가 아닌 것일 뿐이지 테스트하지 말라는 말은 아니였다.
참고서적
단위테스트, 블라디미르 코리코프
'TIL' 카테고리의 다른 글
12/30 TIL C++ 알고리즘을 위한 STL 소소팁 (0) | 2024.12.30 |
---|---|
12/28 TIL 좋은 단위 테스트의 4대 요소 (1) | 2024.12.29 |
12/27 TIL 게임 객체 모델의 종류 (0) | 2024.12.28 |
12/27 TIL 클린 아키텍처 15장~22장 (2) | 2024.12.28 |
12/26 TIL XCode, C++ snippet (0) | 2024.12.27 |