[xUnit 테스트 패턴] 10장 - 결과 검증
10장 - 결과 검증
상태 검증
- 기대 결과 값이 발생했는지 검증
- SUT 실행 후 상태를 검사
- 상태 검증 2가지 방법
- 절차적 상태 검증
- SUT의 최종 상태를 검출해 기대 값과 같은지 검증하는 단언문 작성
- 기대 객체
- 기대 상태를 단언문으로 표현
- 절차적 상태 검증
내장 단언문 사용하기
- assertTure(aBooleanExpression) - 결과 지정 단언문
- assertEquals(expected, actual) - 단순 동등 단언문
- assertEquals(expected, actual, tolerance) - 퍼지 동등 단언문
- 단언문 사용 시 주의점
- 참이어야 하는 모든 것을 검증해야 함
- 단언문의 문서로서의 가치
- 테스트가 실패할 때 실패 메시지로 무엇이 문제인지를 바로 알 수 있으면 좋다.
- 단언 메세지를 메시지 인자로 추가해줘야 한다.
델타 단언문
- 새로 추가된 객체/로우만 검증
- 불확실성에 대처하는 방법
- 관련 테이블/클래스에 대한 ‘스냅샷’을 테스트 시작 시 저장
- 테스트가 끝나면 생성된 객체/로우 집합에서 아까의 ‘스냅샷’을 제거 후 남은 것들을 기대 결과 값과 비교
외부 결과 검증
- 예상 결과와 실제 결과를 파일로 저장한 후 외부 파일 비교 프로그램으로 차이를 확인 하는 것
- 많이 바뀌지 않는 애플리케이션을 회귀 테스트하기 위해 자동화된 인수 테스트용으로 적합
데이터가 많지 않을때에만 실용적
동작 검증
- 동작은 동적이므로 상태 검증보다 훨씬 까다롭다.
절차형 동작 검증
- SUT가 실행될 때 원하는 동작을 잡아낸 후 나중에 쓸 데이터를 저장해 둔다.
- 나중에 테스트에서는 SUT의 출력 값을 해당 기대 결과 값과 하나하나 비교
- 테스트에서 하나의(혹은 여러 단계의) 프로시저를 실행해 동작을 검증
- 테스트에서는 SUT를 실행한 후 동작에 대한 기록을 얻어 단언문으로 검증
기대 동작 명세
- 기대 동작은 보통 레이어-횡단 테스트와 연계해 객체나 컴포넌트의 간접 출력을 검증하는데 쓰인다.
- SUT가 호출할 메소드가 있는 모의 객체를 설정해 SUT를 실행하기 전에 설치한다.
테스트 코드 중복 줄이기
- 테스트 코드 중복은 깨지기 쉬운 테스트, 깨지시 쉬운 픽스처, 높은 테스트 유지 비용을 유발
- 너무 많은 코드로 인해 애매한 테스트의 증상 일 수 있다.
- 검증 개수를 줄이는 기법
- 기대 객체(Expected Object)
- 맞춤 단언문(Custon Assertion)
- 검증 메소드(Verification Method)
기대 객체
- 검증하려는 값들이 여러 변수에 저장된다면 적절한 클래스 객체를 하나 만들어 그 필드 값을 검증하러는 값들로 초기화 할 수 있다.
- 필드 값만 비교할 수 있는 equals 메소드가 있고 기대 객체를 자유자재로 만들 수 있을 때 쓸 수 있다.
- 만일 위와 같은 상황이 안될 때 방법
- 맞춤 단언문을 구현
- 기대 객체 클래스의 equals 메소드에 테스트용 동등을 따로 구현
맞춤 단언문
- 직접 작성해야 하는 도메인 전용 단언문
- 결과 검증 절차를 알기 쉬운 이름 밑에 숨겨줘 결과 검증 로직의 의도를 좀 더 잘 알 수 있게 한다.
- 많은 코드를 제거하므로 애매한 테스트를 막을 수 있다.
static void assertLineItemsEqual(String msg, LineItem exp, LineItem act){
assertEquals(msg + "Inv", exp.getInv(), act.getInv());
assertEquals(msg + "Prod", exp.getProd(), act.getProd());
assertEquals(msg + "Quan", exp.getQuantity(), act.getQuantity());
}
- 맞춤 단언문을 만드는 두가지 방법
- 기존의 복잡한 테스트 코드를 리팩토링 한다.
- 비어있는 단언 메소드를 호출하게 테스트를 작성하고, 필요한 곳들이 파악되면 비어있는 단언 메소드에 적당한 로직을 채워 넣는다.
결과를 설명하는 검증 메소드
void assertInvoiceContainsOnlyThisLineItem(Invoice inv, LineItem expItem){
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
assertLineItemsEqual("", expItem, actual);
}
- 검증 메소드와 맞춤 단언문의 차이점
- 맞춤 단언문
- 단언만 함
- 시그니처는 일반적인 동등 단언문 시그니처(assertSomething(message, expected, actual))
- 검증 메소드
- SUT와 상호작용
- SUT에게 넘겨줄 인자가 필요하므로 형태가 자유분방
- 맞춤 단언문
인자를 받는 테스트와 데이터 주도 테스트
- 인자를 받는 테스트
- 픽스처 설치, SUT 실행, 결과 검증 부분을 뽑아내서 만든다.
- 테스트용 데이터를 테스트 메소드에서 받는다.
- 데이터 주도 테스트
- 자체가 테스트 메소드이고 파일에서 테스트용 데이터를 직접 읽어 들인다.
테스트 내 조건문 로직 피하기
- 테스트 메소드의 코드는 테스트 할 수 없으므로 테스트 내 조건문 로직이 있을 경우 테스트에 대한 신뢰가 떨어짐
if문 제거하기
- 보호 단언문 사용
//보호 단언문 사용 전
Line lineItems = invoice.getLineItems();
if(lineItems.size() == 1){
LineItem expected = new LineItem(invoice, product, 5, new BigDecimal("30"), new BigDecimal("69.96"));
LineItem actItem = (LineItem) lineItems.get(0);
assertEquals("invoice", expected, actItem);
} else {
fail("Invoice should have exactly one line item");
}
//보호 단언문 사용
Line lineItems = invoice.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem expected = new LineItem(invoice, product, 5, new BigDecimal("30"), new BigDecimal("69.96"));
LineItem actItem = (LineItem) lineItems.get(0);
assertEquals("invoice", expected, actItem);
- 보호 단언문의 장점은 테스트 내 조건문 로직이 없어도 테스트 에러를 낼 수 있는 곳에 단언문이 걸리게 할 수 있다는 점
반복문 제거하기
- SUT가 리턴한 컬렉션의 내용을 기대 값과 비교하는 반복문 형태로 테스트 내 조건문 로직이 나타날 수 있다.
- 문제점 3 가지
- 반복문 코드를 완전 자동 테스트로 테스트 할 수 없으므로 테스트 할 수 없는 테스트 코드가 추가 되는 셈
- 의도를 바로 알기 어렵기 때문에 애매한 테스트가 되기 쉬움
- 반복문 작성이 복잡하다보니 개발자가 자체 검사 테스트를 쓰려하지 않아 테스트를 작성하지 않는 개발자가 될 수 있다.
기타 기법
거꾸로, 밖에서 안으로 작업하기
거꾸로 작업하기
란 단언문들을 먼저 작성하는 걸 의미- 단언문의 의도를 보여줄 수 있게 적절한 이름의 지역 변수 값을 단언한다.
- 단언문을 실행하기 위해 채워 넣어야 하는 테스트 코드를 작성한다.
- 즉, 단언문 변수를 먼저 선언하고 그 후에 적절한 내용으로 값들을 초기화 하는 식
밖에서 안으로
또는위에서 아래
로 작업은 일정한 추상 수준을 유지하는 것을 의미- 테스트 유틸리티 메소드를 호출하는 방식으로 테스트를 작성하는 동안에는 SUT의 요구 사항에만 집중하게 한다.
- 객체를 어떻게 생성할지, 결과를 어떻게 검증할지 고민하지 말고 어떤 객체나 결과가 필요한지만 작성 해 둔다.
- 정의하지 않은 채로 먼저 사용하는 유틸리티 메소드는 테스트 자동 로직이 들어갈 장소 역할을 하게 된다.
테스트 주도 개발로 테스트 유틸리티 메소드 작성하기
- 테스트 유틸리티 메소드를 사용하는 테스트 메소드 작성이 끝나면 테스트 유틸리티 메소드를 구현할 시점이다.
- 테스트 유틸리티 메소드의 동작을 검증하는 단위 테스트는 금방 작성하고, 테스트 유틸리티 메소드에 대한 신뢰도 높아진다.
- 필요하지도 않은 경우까지 처리할 수 있게 일반적인 로직을 구현할 필요가 없다.
재사용 가능한 검증 로직을 둘 위치
- 테스트 케이스 클래스 안쪽에 위치
- 테스트 케이스 상위 클래스로 메소드 올리기를 하거나 테스트 도우미 안으로 메소드 옮기기를 하면 재사용성이 올라감