JaCoCo와 애노테이션: 자바 테스트 커버리지의 한계 이해하기

2024. 1. 8. 20:44Project 해축갤/코드개선

728x90

JaCoCo 란?

JaCoCo

JaCoCo는 Java 코드의 커버리지를 체크하는 라이브러리입니다.

여기서 코드의 커버리지란 내 테스트 코드가 내 코드의 몇 퍼센트를 보증하냐!라고 생각하시면 편합니다.

 

테스트코드를 돌리고 그 커버리지 결과를 눈으로 보기 좋도록 html이나 xml, csv 같은 리포트로 생성합니다.

그리고 테스트 결과가 내가 설정한 커버리지 기준을 만족하는지 확인하는 기능도 있습니다. 

 

최근 이러한 JaCoCo를 활용해 코드의 품질을 높이려는 노력을 하던 와중에 특이한 점을 찾았습니다.

특이점

JaCoCo 를 통해 테스트의 커버리지를 측정하던 와중

테스트 코드를 작성했는데도 커버리지가 카운팅이 되지 않는 부분을 발견했습니다.

다음은 문제의 코드입니다.

    @Test
    @DisplayName("updatePopularPosts 메서드가 매시 정각에 실행되어야 한다")
    void shouldTrigger_updatePopularPosts_atEveryHour() throws ParseException {
        // Given - 상황 설정
        String cronExpression = "0 0 * * * *";
        CronTrigger trigger = new CronTrigger(cronExpression);
        Date startTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2023/12/20 00:00:00");
        SimpleTriggerContext context = new SimpleTriggerContext();
        context.update(startTime, startTime, startTime);
        List<String> expectedTimes = Arrays.asList(
                "2023/12/20 01:00:00",
                "2023/12/20 02:00:00",
                "2023/12/20 03:00:00");

        // When - 동작 수행
        for (String expectedTime : expectedTimes) {
            Date nextExecutionTime = trigger.nextExecutionTime(context);

            // Then - 결과 검증
            // 계산된 실행 시간이 예상 시간과 일치하는지 확인
            String actualTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(nextExecutionTime);
            assertThat(actualTime).isEqualTo(expectedTime);
            // 다음 예상 시간 계산을 위해 컨텍스트 업데이트
            context.update(nextExecutionTime, nextExecutionTime, nextExecutionTime);
        }
    }

 

코드에 대한 간략한 설명을 하자면 다음과 같습니다.

1. 상황 설정(Given): '매시 정각'을 의미하는 cron 표현식("0 0 * * * *")을 설정하고, CronTrigger 객체를 생성합니다.
테스트 시작 시간을 "2023/12/20 00:00:00"으로 설정하고, 스케줄링 콘텍스트를 초기화합니다.

2. 예상되는 실행 시간 목록: "2023/12/20 01:00:00", "2023/12/20 02:00:00", "2023/12/20 03:00:00"이 세 가지 시간을 예상 실행 시간으로 설정합니다.

3. 동작 수행(When): Cron 트리거를 사용하여 각 예상 시간에 대해 다음 실행 시간을 계산합니다.

4. 결과 검증(Then): 계산된 다음 실행 시간이 예상 시간과 일치하는지 확인합니다.일치하면 컨텍스트를 업데이트하여 다음 예상 시간을 위한 준비를 합니다.

 

매 정시마다 스케줄링 하도록 하는 함수를 직접적으로 테스트하기보다는

cron 표현식이 제대로 작성이 되었나를 간접적으로 체크하는 테스트코드입니다.

 

이에 대한 JaCoCo는 다음과 결과를 내놓습니다.

(사진)

즉, JaCoCo 는 위 테스트 코드가 제 소스 코드를 커버하지 못한다고 판단을 내린 거죠.

어째서일까요?

JaCoCo 원리

원인은 JaCoCo의 동작 원리에 있습니다. 

JaCoCo 의 동작 원리에 대한 그래프

그림은 JaCoCo 어떻게 작동하는지를 보여줍니다.

JVMTI API -> JaCoCo Agent(Java Agent)

JaCoCo 에이전트 JVM(Java Virtual Machine) 안에서 코드의 실행을 듣는 '리스너(listener)' 역할 합니다.

이때 코드를 실행을 들을 수 있도록 ASM (Java 바이트코드에 조각을 심어 발동되면 listener 가 듣도록 하는 역할)을 통해

Java 바이트코드의 조작을 가해 놓습니다. 그리고 JVM에서 일어나는 이벤트들은 JVM TI(JVM Tool Interface) API 통해 JaCoCo 에이전트로 전달됩니다.

 

정보를 바탕으로 JaCoCo 실행된 코드의 어느 부분이 커버되었는지 분석하고, '덤프 요청(dump request)' 통해 데이터를 추출하여 커버리지 보고서를 생성합니다. JVMTI 자바 가상 머신과 개발 도구 사이의 통신을 가능하게 하는 인터페이스,

여기서는 JaCoCo 에이전트가 이벤트를 듣기 위해 사용됩니다.

 

즉, 정리하자면 Java Byte Code 안을 JaCoCo 가 체크하게 되는 겁니다.

하지만 저의 경우 함수 자체가 아니라 메타 정보인 애노테이션을 테스트하기 때문에 JaCoCo 가 인지를 할 수 없는 거죠.

애노테이션에는 ASM을 통해 조각을 심지 않기 때문에!

정리

  • JaCoCo는 Java ByteCode 안에 코드 조각을 심어 발동 여부를 체크합니다.
  • 그렇기에 메타 정보인 애노테이션은 JaCoCo 가 테스트 하지 못합니다!

출처

728x90