자바/자바 코딩의 기술

자바 코딩의 기술 - 6장 ( 올바르게 드러내기)

끄적끄적 2022. 4. 9. 15:02

코드는 꼭 테스트하라. 아니면 사용자가 하게 된다. 
- 데이브 토마스, 앤드류 헌트

6-1. Given-When-Then으로 테스트 구조화

@Test
void setPlanetarySpeedIs7667() {
    CruiseControl cruiseControl = new CruiseControl();
    cruiseControl.setPreset(SpeedPreset.PLANETARY_SPEED);
    Assertions.assertTrue(7667 == cruiseControl.getTargetSpeedKmh());
}

주석을 추가하고, 띄워쓰기를 넣어서 Given, When, Then을 확실히 인지할 수 있다.

void otherTest() {
    // given
    CruiseControl cruiseControl = new CruiseControl();

    // when
    cruiseControl.setPreset(SpeedPreset.PLANETARY_SPEED);

    // then
    Assertions.assertTrue(7667 == cruiseControl.getTargetSpeedKmh());
}

6-2. 의미있는 Assertion 사용하기

Assertions.assertTrue(7667 == cruiseControl.getTargetSpeedKmh());

assertTrue() 는 false인지만 알려주지만, assertEquals()는 기대한 값과, 실제 나온 값을 로그에서 확인가능하다.

Assertions.assertEquals(7667, cruiseControl.getTargetSpeedKmh());

6-3. 실제 값보다 기대 값을 먼저 보이기

assertEquals호출시 인수의 순서에 주의해야 한다. 오해의 소지가 없도록 기대값과 실제값이 로그에 찍히게 하라.

@Test
void setPlanetarySpeedIs7667() {
    CruiseControl cruiseControl = new CruiseControl();

    cruiseControl.setPreset(SpeedPreset.PLANETARY_SPEED);

    Assertions.assertEquals(7667, cruiseControl.getTargetSpeedKmh());
}

6-4. 합당한 허용값 사용하기

자바에서 float나 double은 부동소수점 표현이므로, 0.1로 표현해도 실제 정확히 0.1이 아니다. 이를 명심하고 equal비교시 소수점 넷째 자리등 허용값을 지정해야 한다.

static final double TOLERANCE = 0.00001;


@Test
void testNewTankIsEmpty() {
    OxygenTank tank = OxygenTank.withCapacity(100);

    Assertions.assertEquals(0, tank.getStatus(), TOLERANCE);
}

@Test
void testFilling() {
    OxygenTank tank = OxygenTank.withCapacity(100);

    tank.fill(5.8);
    tank.fill(5.6);

    Assertions.assertEquals(0.114, tank.getStatus(), TOLERANCE);
}

6-5 예외 처리는 JUnit에 맡기기

테스트시에 Exception이 발생해야 한다면 assertThrows()를 사용하라. Exception 이 발생하면 안된다면 메소드 시그니처뒤 에 throws Exception을 넣어서 전체 스택을 뿌려주도록 하라.

Assertions.assertThrows(IOException.class, when);

6-6. 테스트 설명하기

테스트 실패시 이름만 보고도 내용을 알 수 있도록 메서드 명을 상세화하거나 @DisplayName을 이용해서 테스트 내용을 설명하라. 테스트를 비활성화 할때에도 단순히 @Disabled 만 놓지말고, 왜 비활성화 하는지 등 내용을 설명하라.

@Test
@DisplayName("Expect 44% after filling 22l in an empty 50l tank")
@Disabled("We don't have small tanks anymore! TODO: Adapt for big tanks")

6-7. 독립형 테스트 사용하기

@BeforeEach 를 이용해서 자주 사용되는 부분을 넣어놓으면, 테스트 메서드에서 항상 해당 부분을 먼저 보고 와야 한다.  항상 수행하는게 있으면 별도의 메서드로 뽑아내고, 각각의 @Test 부분에서 호출하는 방식을 쓰자.

@BeforeEach
void setUp() {
    tank = OxygenTank.withCapacity(10_000);
    tank.fill(5_000);
}

@Test
void depressurizingEmptiesTank() {
    tank.depressurize();

    Assertions.assertTrue(tank.isEmpty());
}

@Test
void completelyFillTankMustBeFull() {
    tank.fillUp();

    Assertions.assertTrue(tank.isFull());
}

@BeforeEach부분을 createhalfFilledTank()로 빼 냈다.

static OxygenTank createHalfFilledTank() {
    OxygenTank tank = OxygenTank.withCapacity(10_000);
    tank.fill(5_000);
    return tank;
}

@Test
void depressurizingEmptiesTank() {
    OxygenTank tank = createHalfFilledTank();

    tank.depressurize();

    Assertions.assertTrue(tank.isEmpty());
}

@Test
void completelyFillTankMustBeFull() {
    OxygenTank tank = createHalfFilledTank();

    tank.fillUp();

    Assertions.assertTrue(tank.isFull());
}

6-8. 테스트 매개변수화

@Test
void testConversionRoundTrip() {
    assertRoundTrip(1);
    assertRoundTrip(1_000);
    assertRoundTrip(9_999_999);
}

private void assertRoundTrip(int kilometers) {
    Distance expectedDistance = new Distance(
            DistanceUnit.KILOMETERS,
            kilometers
    );

    Distance actualDistance = expectedDistance
            .convertTo(DistanceUnit.MILES)
            .convertTo(DistanceUnit.KILOMETERS);

    Assertions.assertEquals(expectedDistance, actualDistance);
}

@ParameterizedTest 와 @ValueSource를 활용해서 여러 파라미터로 반복 수행해야 할 경우

@ParameterizedTest(name = "#{index}: {0}km == {0}km->mi->km")
@ValueSource(ints = {1, 1_000, 9_999_999})
void testConversionRoundTrip(int kilometers) {
    Distance expectedDistance = new Distance(
            DistanceUnit.KILOMETERS,
            kilometers
    );

    Distance actualDistance = expectedDistance
            .convertTo(DistanceUnit.MILES)
            .convertTo(DistanceUnit.KILOMETERS);

    Assertions.assertEquals(expectedDistance, actualDistance);
}

6-9. 경계 케이스 다루기

@Test
void testValidTransmission() {
    TransmissionParser parser = new TransmissionParser();

    Transmission transmission = parser.parse("032Houston, UFO sighted!");

    Assertions.assertEquals(32, transmission.getId());
    Assertions.assertEquals("Houston, UFO sighted!",
            transmission.getContent());
}

위와 같은 테스트의 경우 null값, 특수문자가 포함된 String 등 경계 케이스를 테스트를 다루어야 한다. int값이면 음수, Integer.MAX_VALUE, Integer.MIN_VALUE 등의 경계값이 있다.

@Test
void testValidTransmission() {
    TransmissionParser parser = new TransmissionParser();

    Transmission transmission = parser.parse("032Houston, UFO sighted!");

    Assertions.assertEquals(32, transmission.getId());
    Assertions.assertEquals("Houston, UFO sighted!",
            transmission.getContent());
}

@Test
void nullShouldThrowIllegalArgumentException() {
    Executable when = () -> new TransmissionParser().parse(null);
    Assertions.assertThrows(IllegalArgumentException.class, when);
}

@Test
void malformedTransmissionShouldThrowIllegalArgumentException() {
    Executable when = () -> new TransmissionParser().parse("t͡ɬɪŋɑn");
    Assertions.assertThrows(IllegalArgumentException.class, when);
}

 

 

반응형