코드 커버리지 적용(Jacoco)

Jae Hun Lee·2023년 4월 7일
0

코드 커버리지?

코드 커버리지란 소프트웨어 테스트의 측정 항목 중 하나로, 테스트를 수행했을 때 얼마나 많은 코드가 실행되었는지를 나타내는 지표입니다. 즉, 소스 코드 중에서 얼마나 많은 부분이 테스트 케이스에 의해 실행되었는지를 백분율로 나타내는 것입니다.

코드 커버리지의 종류

  • 라인 커버리지 : 소스 코드의 각 라인이 실행되는 비율
  • 브랜치 커버리지 : 브랜치 커버리지는 if문, switch문 등의 분기문에서 모든 경우의 수가 테스트되는 비율
  • 메소드 커버리지 : 클래스 내의 메소드 중에서 테스트된 메소드의 비율
  • 클래스 커버리지 : 소스 코드 내의 모든 클래스 중에서 테스트된 클래스의 비율

왜 사용할까?

  • 코드 커버리지는 소프트웨어 개발에서 테스트된 코드의 양을 측정하는 데 사용됩니다. 이는 테스트되지 않은 코드가 얼마나 남았는지, 즉 테스트를 통해 확인되지 않은 버그가 있는지를 파악하는 데 도움이 됩니다.
  • 코드 커버리지를 사용하면 개발자들은 자신이 작성한 코드를 얼마나 잘 테스트했는지를 확인할 수 있습니다. 더 나은 코드 커버리지를 달성하면 코드의 안정성과 신뢰성을 높일 수 있으며, 잠재적인 버그를 더 빨리 발견하고 수정할 수 있습니다.

Java 코드 커버리지 도구

  1. JaCoCo
    • 실행 속도가 빠르며 정확도가 높아 코드 커버리지 측면에서 우수한 성능을 보입니다.
    • XML, CSV, HTML, JSON 등 다양한 형식으로 리포트를 출력할 수 있습니다.
    • 레퍼런스가 가장 많다
  2. Cobertura
    • 정확도가 높지만 JaCoCo보다는 느린 속도를 보입니다.
    • HTML, XML, CSV 등 다양한 형식으로 리포트를 출력할 수 있습니다.
  3. Emma
    • JaCoCo, Cobertura에 비해 속도가 느리지만 정확도가 높아 코드 커버리지 측면에서 우수한 성능을 보입니다.
    • XML, HTML 등 다양한 형식으로 리포트를 출력할 수 있습니다.

어떤도구를 사용할까 ?

  • 처음 기술을 도입하기때문에 레퍼런스가 가장 중요하다고 생각하여 Jacoco를 도입하였다.

Jacoco 적용하기

플러그인 설정하기

build.gradle

  • build.gradle의 plugins부분에 id ‘jacoco’를 추가해준다
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.9'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'jacoco'
}

JacocoTestReportsTesk 설정

  • 테스트 결과를 리포르로 저장해주는 부분을 설정해준다.
  • QueryDsl을 사용하지만 코드 커버리지엔 사용하지않기때문에 Q클래스를 제외 해준다.
  • 이 외의 dto, global등도 제외해준다.
jacocoTestReport {
    //레포트 파일 생성
    reports {
        html.enabled true
        xml.enabled false
        csv.enabled true
    }
    // jacocoReport 에서 q 클래스는 제외
    def Qdomains = []
    for(qPattern in "**/QA" .. "**/QZ"){
        Qdomains.add(qPattern+"*")
    }

    afterEvaluate {
        // 레포트에서 제외 항목 추가
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it,
                    exclude: ["**/*Application*",
                              "**/_global/*",
                              "**/dto/*",
                              "**/redis/*"
                    ] + Qdomains)
        }))
    }

    finalizedBy 'jacocoTestCoverageVerification'
}

jacocoTestCoverageVerification 설정

  • 코드 커버리지를 만족하는지 여부를 확인하는 설정이다.
  • 최소 커버리지 수준을 설정할수있으며 통과하지 못하면 실패한다.
  • violationRules 메서드는 커버리지 통과 기준을 설정 한다.
jacocoTestCoverageVerification {
    def Qdomains = []
    for (qPattern in "*.QA".."*.QZ") {  // qPattern = "*.QA","*.QB","*.QC", ... "*.QZ"
        Qdomains.add(qPattern + "*")
    }

    violationRules {
        rule {
            enabled = true
            element = 'CLASS'
						// includes = []

            limit {
                counter = 'BRANCH'
                value = 'COVEREDRATIO'
                minimum = 0.90
            }

            limit {
                counter = 'LINE'
                value = 'COVEREDRATIO'
                minimum = 0.80
            }

            limit {
                counter = 'LINE'
                value = 'TOTALCOUNT'
                maximum = 200
            }
            excludes = [ "**/*Application*",
                         "**/_global/*",
                         "**/dto/*",
                         "**/redis/*"
            ] + Qdomains
        }
    }
}

enabled

  • 이 태스크가 활성화되어 있는지 여부를 지정하는 프로퍼티입니다. 기본값은 true
    입니다.

elements

ICoverageNode.ElementType (JaCoCo 0.8.10.202304030907 API)

  • 검증할 코드 요소를 지정합니다. elements 는 클래스, 메서드, 라인 등 코드의 여러 요소들을 나타내며, includes 와 함께 사용됩니다. 예를 들어, elements = ['CLASS'] 는 테스트 대상으로 클래스들만 선택하여 검증을 수행하겠다는 의미입니다.
  1. BUNDLE : 묶음 커버리지(Bundle Coverage)를 나타냅니다. 이는 프로젝트의 모든 바이트코드 중 얼마나 많이 커버리지가 측정되었는지를 나타냅니다. 이 요소는 대개 프로젝트의 전체적인 커버리지를 확인하기 위해 사용됩니다.
  2. CLASS : 클래스 커버리지(Class Coverage)를 나타냅니다. 이는 프로젝트의 모든 클래스 중 몇 개의 클래스가 테스트되었는지를 나타냅니다.
  3. GROUP : 그룹 커버리지(Group Coverage)를 나타냅니다. 이는 프로젝트의 패키지 구조를 기반으로 각 패키지에 대한 커버리지를 나타냅니다.
  4. METHOD : 메서드 커버리지(Method Coverage)를 나타냅니다. 이는 프로젝트의 모든 메서드 중 몇 개의 메서드가 테스트되었는지를 나타냅니다.
  5. PACKAGE : 패키지 커버리지(Package Coverage)를 나타냅니다. 이는 프로젝트의 모든 패키지 중 몇 개의 패키지가 테스트되었는지를 나타냅니다.
  6. SOURCEFILE : 소스 파일 커버리지(Source File Coverage)를 나타냅니다. 이는 프로젝트의 모든 소스 파일 중 몇 개의 파일이 테스트되었는지를 나타냅니다.

includes

ICoverageNode.CounterEntity (JaCoCo 0.8.10.202304030907 API)

  • 파일 경로를 패턴으로 지정합니다. 예를 들어, `include '/service/*'**는 **service`**
    패키지의 모든 클래스를 커버리지 검증 대상으로 지정합니다

counter

ICoverageNode.CounterEntity (JaCoCo 0.8.10.202304030907 API)

  • 검증할 커버리지 항목을 지정합니다
  1. BRANCH : 분기 커버리지(Branch Coverage)를 나타냅니다. 이는 코드에서 조건문, switch 문 등의 브랜치(가지)가 얼마나 많이 실행되었는지를 나타냅니다. 즉, 브랜치가 있는 조건문에서 모든 경우의 수를 실행해보았는지 여부를 확인하는 지표입니다.
  2. CLASS : 클래스 커버리지(Class Coverage)를 나타냅니다. 이는 프로젝트의 모든 클래스 중 몇 개의 클래스가 테스트되었는지를 나타냅니다.
  3. COMPLEXITY : 코드 복잡성(Complexity)을 나타냅니다. 이는 코드 내의 제어 흐름(예: if문, loop문 등)이 얼마나 복잡한지를 나타내는 지표입니다.
  4. INSTRUCTION : 명령어 수(Instruction Coverage)를 나타냅니다. 이는 코드의 모든 명령어 중 몇 개가 실행되었는지를 나타냅니다.
  5. LINE : 라인 커버리지(Line Coverage)를 나타냅니다. 이는 코드에서 얼마나 많은 라인이 테스트되었는지를 나타냅니다.
  6. METHOD : 메서드 커버리지(Method Coverage)를 나타냅니다. 이는 프로젝트의 모든 메서드 중 몇 개의 메서드가 테스트되었는지를 나타냅니다.

value

ICounter.CounterValue (JaCoCo 0.8.10.202304030907 API)

  • 검증할 커버리지 값의 범위를 지정합니다.
  1. COVEREDCOUNT : 커버된 항목의 수를 나타냅니다. 예를 들어, COVEREDCOUNT가 10인 경우, 해당 항목에 대해 10개의 코드 라인이 테스트를 통과했다는 것을 의미합니다.
  2. COVEREDRATIO : 커버된 항목의 비율을 나타냅니다. 예를 들어, COVEREDRATIO가 80%인 경우, 해당 항목의 코드 라인 중 80%가 테스트를 통과했다는 것을 의미합니다.
  3. MISSEDCOUNT : 커버되지 않은 항목의 수를 나타냅니다. 예를 들어, MISSEDCOUNT가 5인 경우, 해당 항목에 대해 5개의 코드 라인이 테스트를 통과하지 못했다는 것을 의미합니다.
  4. MISSEDRATIO : 커버되지 않은 항목의 비율을 나타냅니다. 예를 들어, MISSEDRATIO가 20%인 경우, 해당 항목의 코드 라인 중 20%가 테스트를 통과하지 못했다는 것을 의미합니다.
  5. TOTALCOUNT : 전체 항목의 수를 나타냅니다. 예를 들어, TOTALCOUNT가 20인 경우, 해당 항목에 대해 총 20개의 코드 라인이 존재한다는 것을 의미합니다.

minimum

  • 테스트 코드 커버리지에 대한 최소 기준값을 설정하는 데 사용됩니다. 이 옵션은 검증할 코드 커버리지 결과의 최소 요구 값을 지정하여 테스트가 통과되는지 여부를 결정합니다.

exclude

  • 코드 커버리지 검증에서 제외할 파일을 지정하는 데 사용되는 옵션입니다. 이 옵션은 파일 경로를 패턴으로 지정합니다.
  • 예를 들어, `exclude '/Test'**는 이름이 **Test`**로 끝나는 모든 클래스를 검증 대상에서 제외합니다. 이렇게 하면 테스트 코드나 mock 클래스 같이 실제로 실행되지 않는 코드를 코드 커버리지 검증에서 제외할 수 있습니다.

테스트 진행

  • 테스트 코드를 작성하고 Jacoco로 확인을 해보자

테스트 코드 작성

@ExtendWith(MockitoExtension.class)
@DisplayName("유저 테스트")
class UserServiceTest {
    @InjectMocks
    private UserService userService;
    @Mock
    private UserRepository userRepository;
    @Mock
    private PasswordEncoder passwordEncoder;

    @Test
    @DisplayName("로그인-성공")
    void loginTest() {
        LoginRequestDto loginRequestDto = LoginRequestDto.of("test1", "123123");
        HttpServletResponse responseMock = mock(HttpServletResponse.class);
        User user = User.of("test1", "1234");
        when(userRepository.findByUserId(any())).thenReturn(Optional.of(user));
        when(passwordEncoder.matches(any(), any())).thenReturn(true);

        userService.login(loginRequestDto, responseMock);
    }

    @Test
    @DisplayName("로그인-아이디-실패")
    void loginFailIdTest() {
        LoginRequestDto loginRequestDto = LoginRequestDto.of("test1", "123123");
        HttpServletResponse responseMock = mock(HttpServletResponse.class);
        when(userRepository.findByUserId(any())).thenReturn(Optional.empty());
        Exception exception = assertThrows(CustomException.class, () ->
                userService.login(loginRequestDto, responseMock)
        );
        assertEquals(exception.getMessage(), ErrorType.NOT_MATCHING_INFO.getMsg());

    }

    @Test
    @DisplayName("로그인-패스워드-실패")
    void loginFailPwTest() {
        LoginRequestDto loginRequestDto = LoginRequestDto.of("test1", "123123");
        HttpServletResponse responseMock = mock(HttpServletResponse.class);
        User user = User.of("test1", passwordEncoder.encode("5678"));
        when(userRepository.findByUserId(any())).thenReturn(Optional.of(user));
        Exception exception = assertThrows(CustomException.class, () -> {
            userService.login(loginRequestDto, responseMock);
        });
        assertEquals(exception.getMessage(), ErrorType.NOT_MATCHING_INFO.getMsg());
    }
    
}

테스트 실행

  • test를 꼭 실행 후 jacocoTestReport를 실행해야한다

  • 아래와 같은 화면을 만난다면 위에서 설정한 조건을 만족하지 못했을 경우 볼 수 있다.

  • 테스트가 성공했다면

경로\build\reports\jacoco\test\html\index.html에서 테스트 커버리지를 확인 가능하다.

붉은색으로 표기된 항목은 테스트를 작성하지 않은 코드이고

초록색은 테스트를 작성한 코드로

아래 사진 처럼 확인이 가능하다!

참조

코드 분석 도구 적용기 - 1편, 코드 커버리지(Code Coverage)가 뭔가요?

profile
기록을 남깁니다

0개의 댓글