/ JACOCO, ANDROID

JaCoCo를 사용하여 안드로이드 프로젝트 유닛 테스트 커버리지 측정하기

단말기를 연결하거나 에뮬레이터를 실행해야 수행 가능한 Instrumentation Test와 달리, 유닛 테스트는 자체적으로 테스트를 수행할 수 있어 매우 편리합니다.

안드로이드 스튜디오에서도 유닛 테스트를 지원하게 되어 이전보다 더 편리하게 테스트를 작성할 수 있게 되었는데, 테스트 커버리지(Coverage)를 측정은 자체적으로 지원하지 않아 별도 설정이 필요합니다.

테스트 코드 작성

테스트 커버리지는 전체 테스트 가능한 코드 중 테스트 된 코드의 비중을 측정하는 작업이므로, 테스트 코드 없이는 수행되지 않습니다.

따라서, 테스트 코드를 작성하기 위해 먼저 다음과 같이 간단한 클래스를 작성합니다.

[Calculator.java]

public class Calculator {

    public static int sum(int a, int b) {
        return a + b;
    }

    public static int sub(int a, int b) {
        return a - b;
    }
}

다음, Calculator 클래스의 테스트 코드를 작성합니다. 유닛 테스트로 수행할 코드이므로 src/androidTest/java가 아닌 src/test/java 하위 폴더에 추가해야 하는 것에 유념하세요.

[CalculatorTest.java]

public class CalculatorTest {

    @Test
    public void testSum() {
        assertEquals(5, Calculator.sum(2, 3));
    }

}

빌드 스크립트 수정

유닛 테스트를 수행할 모듈의 빌드 스크립트(build.gradle)에 다음과 같이 테스트 커버리지를 측정하는 태스크를 추가합니다. 여기에서는 JaCoCo를 사용하여 커버리지를 측정합니다.

apply plugin: 'com.android.application'

android {
    ...
}

dependencies {
    ...
}

apply plugin: 'jacoco'

jacoco {
    reportsDir = file("${buildDir}/reports")
}

task coverageReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"

    def coverageSourceDirs = ['src/main/java']

    classDirectories = fileTree(
            dir: "${buildDir}/intermediates/classes/debug",
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/BuildConfig.*',
                       '**/Manifest*.*',
                       'com/android/**/*.class']
    )

    sourceDirectories = files(coverageSourceDirs)
    executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }
}

추가해 준 구문을 부분별로 조금 더 자세히 살펴봅시다.

JaCoCo 플러그인 설정

apply plugin: 'jacoco'

jacoco {
    reportsDir = file("${buildDir}/reports")
}

빌드 스크립트에서 JaCoCo를 사용할 수 있도록 하고, 측정 결과가 저장될 경로를 지정합니다.

커버리지 측정 태스크 정의

task coverageReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
    group = "Reporting"
    description = "Generate Jacoco coverage reports"

    def coverageSourceDirs = ['src/main/java']

    classDirectories = fileTree(
            dir: "${buildDir}/intermediates/classes/debug",
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/BuildConfig.*',
                       '**/Manifest*.*',
                       'com/android/**/*.class']
    )

    sourceDirectories = files(coverageSourceDirs)
    executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec")

    reports {
        xml.enabled = true
        html.enabled = true
    }
}
  • dependsOn: 테스트 수행 후 커버리지 측정이 진행될 수 있도록, 유닛 테스트를 수행하는 태스크 이름으로 지정합니다. 사용하는 안드로이드 그래들 빌드 플러그인 버전에 따라 다르게 지정해야 하며, 사용 중인 플러그인 버전은 루트 프로젝트의 build.gradle에서 확인할 수 있습니다. 버전별로 지정해야 하는 이름은 다음과 같습니다.
    • 1.2.3 이하: testDebug
    • 1.3.0 이상: testDebugUnitTest
  • coverageSourceDirs(sourceDirectories): 커버리지를 측정할 소스 디렉터리를 지정합니다.
  • classDirectories: 소스 디렉터리 내 클래스를 컴파일한 결과인 *.class 파일이 있는 디렉터리를 지정합니다. 커버리지 측정에서 제외해야 하는 클래스(R, 안드로이드 서포트 라이브러리 등)는 제외합니다.
  • executionData: 커버리지 측정 결과를 저장할 파일 이름을 지정합니다. 플러그인 버전에 따라 다르게 지정합니다.
    • 1.2.3 이하: testDebug.exec
    • 1.3.0 이상: testDebugUnitTest.exec
  • reports: 커버리지 결과 리포트 형식을 지정합니다.

커버리지 측정

./gradlew :app:coverageReport를 사용하여 커버리지를 측정합니다. (Windows일 경우 gradlew.bat :app:coverageReport)

측정 결과는 app/build/reports/coverageReport에 생성되며, html 폴더 내에서 다음과 같이 HTML 형태로 된 측정 결과를 확인할 수 있습니다.

Coverage Report

본 포스트에서 사용한 예제 프로젝트의 전체 소스 코드는 아래에서 확인 가능합니다.

kunny

커니

안드로이드와 오픈소스, 코틀린(Kotlin)에 관심이 많습니다. 한국 GDG 안드로이드 운영자 및 GDE 안드로이드로 활동했으며, 현재 구글에서 애드몹 기술 지원을 담당하고 있습니다.

Read More