단위 테스트를 작성해보자 라고 마음먹고 어떻게 써야하는지 검색했을 때 정말 많이 봤던 패턴이 given-when-then이다.
더 근본있는(?) 정보를 위해 클린코드에서 '단위테스트' 장을 보게되었다.
이 포스트는 도메인에 특화된 DSL 테스트언어 및 이중표현 등의 내용은 차치하고, 가장 쓸모있는 관련 내용만 내 코드와 함께 버무린 글이다.
바로 가독성이다. 명료하고 단순하고 표현을 풍부하게 하면 가독성이 좋아진다(말은 쉽지)
내가 테스트코드를 처음 써본 경험은 우테코 프리코스였다. 과거를 반면교사 삼고 싶었지만 간단한 과제였기에 생각보다 간결하게 작성한 듯하다...
DiscountCalculator 객체를 만들고 -> calculateWeekendDiscount 메소드를 실행해 할인가를 도출하고 -> 할인가가 예상가와 일치하는지 확인
일반화하자면
테스트 자료를 만들고 -> 메서드를 실행해서 테스트 자료를 조작하고 -> 조작한 결과가 올바른지 확인하는 과정이다.
class DiscountCalculatorTest {
@ParameterizedTest
@MethodSource("generateWeekendDiscountArgument")
fun `주말 할인가격이 일치하는지 확인 테스트`(date: Int, menus: List<Menu>, expected: Int) {
val discountCalculator = DiscountCalculator(date, menus)
val result = discountCalculator.calculateWeekendDiscount()
assertThat(result).isEqualTo(expected)
}
}
이 말은 테스트 코드 뿐만 아니라 클린코드의 정신 그 자체인 문장같았다.
클린 코드에 있던 코드예제와 유사하게 쓰자면 아래와 같다.
@Test
public void turnOnCoolerAndBlowerIfTooHot() throws Exception{
tooHot();
assertEquals("CoolerAndBlower", hw.getState());
}
public void turnOnHeaterAndBlowerIfTooCold() throws Exception{
tooHot();
assertEquals("HeaterAndBlower", hw.getState());
}
밑의 테스트코드는 내가 썼던 것이다.
차를 달려!
랩보여줘!
그리고 결과를 확인ㅎ...이게 뭐지?
@Test
fun `1번의 이동기회마다 모든 자동차의 경주 결과를 보여주는지 확인`() {
carLane.runCars()
carLane.showLap()
Assertions.assertThat(output.contains("a :\nb :\nc :\n"))
}
저 알 수 없는 "a :\nb :\nc :\n"가 문제이다. 문제가 생각이 잘 안나지만, lap을 돌은 결과를
a:1
b:2
c:3
이렇게 보여주려고 한 거라면, 문자열 "a :\nb :\nc :\n"를 lapResultForm라는 변수로 저장해서
아래와 같이 썻을 것 같다.
@Test
fun `1번의 이동기회마다 모든 자동차의 경주 결과를 보여주는지 확인`() {
carLane.runCars()
carLane.showLap()
Assertions.assertThat(output.contains(lapResultForm))
}
JUnit으로 테스트 코드를 짤 때는 함수마다 assert문을 단 하나만 사용하라는 학파가 있다고 한다. 확실히 결론이 하나니까 이해가 쉽긴하다. 하지만 모든 테스트케이스에
테스트 당 개념 하나만 두면 자연스럽게 잡다한 개념을 연속으로 테스트하는 함수는 작성하지 않게 되기 때문이다.
자주돌려야 하니까!
각 테스트는 서로 의존하면 안 된다
어떤 환경에서도 반복가능
성공 아니면 실패다. 절대 로그 파일을 읽게 해선 안됨
테스트하려는 실제코드 구현 직전에 한다