비동기 코드의 타이밍 테스트 하기 (with Awaitility)

주싱·2023년 3월 14일
9

더 나은 테스트

목록 보기
15/16

Image by Freepik

타이밍을 테스트하는 요구사항

비동기적으로 동작하는 코드를 테스트 하기 위해서는 시간이라는 변수를 다룰 수 있어야 합니다. 크게 아래 3가지 범주의 요구가 있습니다.

  1. 특정 시간 이내에 조건을 만족하는지 확인한다.
  2. 특정 시간 이후에 조건을 만족하는지 확인한다.
  3. 특정 시간 동안에 연속해서 조건을 만족하는지 확인한다.

유용한 도구

Awaitility 라는 도구를 사용하면 비동기 코드의 타이밍을 쉽게 테스트할 수 있습니다. 이제 위 세 가지 요구사항을 테스트하기 위한 Awaitility 예제 코드를 작성해 보겠습니다. 각 요구사항 별로 성공 및 실패 케이스에 대한 테스트 코드를 작성합니다.

전체 코드는 GitHub에서 확인할 수 있습니다.

1. 특정 시간 이내에 조건 만족

atMost() 메서드를 활용하면 특정 시간 이내에 조건을 만족하는지 확인할 수 있습니다.

@Test
void atMostSuccess() {
    AtomicInteger value = new AtomicInteger(0);

    // When: 1초 후에 조건 만족하도록 설정
    executor.submit(() -> {
        Thread.sleep(1000);
        value.set(1);
        return true;
    });

    // Then: 2초 이내에 조건을 만족하여 성공
    await().atMost(2000, TimeUnit.MILLISECONDS)
            .until(() -> value.get() == 1);
}

@Test
void atMostFail() {
    AtomicInteger value = new AtomicInteger(0);

    // When:  1초 후에 조건 만족하도록 설정
    executor.submit(() -> {
        Thread.sleep(1000);
        value.set(1);
        return true;
    });

    // Then: 500msec 이내에 조건을 만족하지 못해 예외 발생
    assertThrows(ConditionTimeoutException.class, () -> {
        await().atMost(500, TimeUnit.MILLISECONDS)
                .until(() -> value.get() == 1);
    });
}

2. 특정 시간 이후에 조건 만족

atLeast() 메서드를 활용하면 특정 시간 이후에 조건을 만족하는지 확인할 수 있습니다. 다르게 생각하면 특정 시간 이전에는 조건을 만족해서는 안되는 상황을 테스트할 수 있습니다.

@Test
void atLeastSuccess() {
    AtomicInteger value = new AtomicInteger(0);

    // When: 1초 후에 조건 만족하도록 설정
    executor.submit(() -> {
        Thread.sleep(1000);
        value.set(1);
        return true;
    });

    // Then: 1초 이후에 조건을 만족하여 성공
    await().atLeast(900, TimeUnit.MILLISECONDS)
            .until(() -> value.get() == 1);
}

@Test
void atLeastFail() {
    AtomicInteger value = new AtomicInteger(0);

    // When: 1초 후에 조건 만족하도록 설정
    executor.submit(() -> {
        Thread.sleep(1000);
        value.set(1);
        return true;
    });

    // Then: 1.1초 이전에 조건을 만족하여 예외 발생
    assertThrows(ConditionTimeoutException.class, () -> {
        await().atLeast(1100, TimeUnit.MILLISECONDS)
                .until(() -> value.get() == 1);
    });
}

3. 특정 시간 동안(During) 연속해서 조건 만족

during() 메서드를 활용하면 특정 기간 동안 연속해서 조건을 만족하는지 확인할 수 있습니다.

@Test
void duringSuccess() {
    AtomicInteger value = new AtomicInteger(0);

    // When: 500ms 동안만 조건을 만족하는 작업 실행
    executor.submit(() -> {
        value.set(1);
        Thread.sleep(500);
        value.set(0);
        Thread.sleep(500);
        return true;
    });

    // Then: 약 500ms 동안 조건을 만족
    await().during(400, TimeUnit.MILLISECONDS)
            .atMost(1000, TimeUnit.MILLISECONDS)
            .until(() -> value.get() == 1);
}

@Test
void duringFail() {
    // Given
    AtomicInteger value = new AtomicInteger(0);

    // When: 300ms 동안만 조건을 만족하는 작업 실행
    executor.submit(() -> {
        value.set(1);
        Thread.sleep(300);
        value.set(0);
        Thread.sleep(700);
        return true;
    });

    // Then: 약 500ms 동안 조건 만족 실패
    assertThrows(ConditionTimeoutException.class, () -> {
        await().during(400, TimeUnit.MILLISECONDS)
                .atMost(1000, TimeUnit.MILLISECONDS)
                .until(() -> value.get() == 1);
    });
}

마치며

Awaitility 라는 프레임워크를 알게되어 감사합니다. 짧은 글이지만 필요한 분들께 도움이 되면 좋겠습니다.

관련 글

profile
소프트웨어 엔지니어, 일상

0개의 댓글