Kotlin Coverage Test – Static Analysis with Jenkins

Kotlin으로 모듈을 작성하다 보면, 모듈의 각 코드에 대한 단위 테스트와 코드 분석을 진행할 필요가 있다.

특히 그 모듈은 구(공 모양의 도형) 상에 존재하는 x개의 데이터 셋에 대해 y 를 제공했을 때, 데이터 셋 상에서 y에 최근접해있는 데이터를 찾는 모듈이었기 때문이었다.

그래서 모듈을 구성하는 모든 코드에 대한 단위 테스트를 진행하여 최대한 모듈 상의 버그를 없애고 (물론, 로직을 구성하는 최하위 알고리즘 상에 문제는 생길 수는 있다. 다만, 서울에서 멀리 떨어진 지역의 데이터이기 때문에 검증하기도 많이 어렵다.) 퍼포먼스, 보안 상 문제가 될 수 있는 모든 가능성을 없앨 필요가 있었다.

이 글에서는 그 과정에서 도입한 Spek Framework, Jacoco Report, Detekt 에 대해 알아보고, 이 세 개를 Jenkins에 연동하여 Jenkins dashboard 에서 해당 리포트에 대해 보여주는 과정을 살펴보려 한다.

Spek Framework

Spek Framework란 코틀린용 유닛 테스트 프레임워크로 Kotlin의 기여자들이 관리하는 프레임워크이다. (JetBrains 공식 프로젝트는 아니나, JetBrains 가 개발한 흔적은 있다.) 현재는 2.0.0-RC1 버전으로, 특징적인 점이라면 DSL로 유닛 테스트를 진행할 수 있다는 점이다.

class SomeTest: Spek({
    describe("Some test") {
        it("Check value is something") {
            // TEST BODY
        }
    }
})

이러한 구조를 가지고 있어 JUnit와 다른 테스트 프레임워크와는 다르게 쉬운 구조를 가지고 있다.

(이렇게 보면, Ktor도 그렇고 JetBrains 가 개발한 프레임워크들은 DSL를 매우 강조하는 것 같다. 과연 그것이 좋은지는 아직도 잘 모르겠긴 하다.)

임포트는 다음과 같이 진행한다.

buildscript {
    ext.kotlin_version = '1.3.11'
    ext.spek_version = '2.0.0-rc.1'
    ext.junit_version = '4.12'
​
    repositories {
        jcenter()
    }
​
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
​
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
    testImplementation "org.spekframework.spek2:spek-dsl-jvm:$spek_version"
    testRuntimeOnly "org.spekframework.spek2:spek-runner-junit5:$spek_version"
    testRuntimeOnly "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
    testCompile("org.assertj:assertj-core:3.11.1")
    testCompile "junit:junit:$junit_version"
}
​
test {
    useJUnitPlatform {
        includeEngines 'spek2'
    }
}

이렇게 작성한 테스트 코드는 gradlew test 로 확인이 가능하다.

Jacoco Report

Jacoco Report는 Java Code Coverage를 구현하는 데 사용하는 툴킷으로, Line, Branch에 대한 Coverage를 제공한다.

임포트는 다음과 같이 한다.

apply plugin: 'jacoco'
​
test {
    useJUnitPlatform {
        includeEngines 'spek2'
    }
    jacoco {
        destinationFile = file("${buildDir}/jacoco/test.exec")
    }
}
​
test.finalizedBy(jacocoTestReport)
​
jacoco {
    // You may modify the Jacoco version here
    toolVersion = "0.8.2"
}
​
jacocoTestReport {
    // Adjust the output of the test report
    reports {
        xml.enabled true
        csv.enabled false
    }
}

gradlew test 로 테스트를 구동했을 경우, destinationFile로 제공한 경로에 exec 파일이 저장된다. 이 exec 파일은 Jacoco 가 생성한 리포트 파일이 포함되며, gradlew jacocoTestReport 명령어로 리포트 파일을 생성할 수 있다. (여기서는 xml, html 리포트를 생성한다.)

Detekt

Detekt는 코틀린에 대한 Static Analysis를 제공하는 툴킷으로, 코틀린으로 코드를 작성함에 있어 피해야하는 패턴들에 대해 감지, 특정 패턴들의 가중치를 파악해서 일정 이상이면 build 자체가 failed 되게 할 수 있어 코드 본연의 문제를 좀 더 파악할 수 있게 하는 툴킷이다.

특히 Codacy (https://www.codacy.com/) 에서 코틀린 프로젝트를 임포트 했을 때 사용하는 분석 툴킷으로, 많은 사람들에 의해 검증된 툴킷이기도 하다.

임포트 과정은 프로젝트 사이트(https://arturbosch.github.io/detekt/) 에 설명되어 있다.

detekt의 경우 gradlew detekt 로 통해 테스트를 진행할 수 있다. 이 때 report는 build/reports/detekt 에 detekt.html 에 저장되며 이 리포트 파일을 참고로 해서 파악할 수 있다.

Jenkins로 실행

위에서 살펴본 Spek Framework, Jacoco, Detekt를 빌드마다 실행하기 위해서 JenkinsFile에 해당 과정을 구현했다.

stage('Test Analysis') {
      parallel {
        stage('Static Analysis') {
          steps {
            sh './gradlew detekt'
            publishHTML(target: [reportDir:'build/reports/detekt/', reportFiles: 'detekt.html', reportName: 'Detekt report'])
          }
        }
        stage('Unit Test') {
          steps {
            sh './gradlew cleanTest test'
            sh './gradlew jacocoTestReport'
            publishHTML(target: [reportDir:'build/reports/jacoco/test/html', reportFiles: 'index.html', reportName: 'Code Coverage'])
          }
        }
      }
    }

gradlew build 가 끝난 다음, 이 단계를 실행하게 하면 된다. publishHTML은 특정 Html 파일을 Jenkins 메뉴 상에 표시해서 쉽게 볼 수 있게 한다.

Jenkins에 테스트 결과에 대한 Output 출력

Test Code를 작성할 때 println 메서드로 Test 콘솔에 Output를 출력할 수 있으나, 위 JenkinsFile를 가지고 실행하면 Output가 나오지 않는다.

이 때는 https://stackoverflow.com/a/36130467 링크에 있는 답변을 사용하면 된다.

최종 결과

전체 파이프라인 통과
각 테스트에 대해 PASSED / FAILED / SKIPPED 등이 표시된다. 만일 Output가 있을 경우 그 아래에 표시된다.
마지막에 총 결과가 표시된다.
Detekt 분석 결과는 콘솔에도 표시된다. 물론, 리포트에도 볼 수 있다.
Jenkins 의 메뉴에 Code Coverage, Detekt Report가 생긴 것을 확인할 수 있다.
Detekt가 생성한 report 파일이다.
Jacoco가 생성한 Coverage Report로, Line Coverage는 99%, Branch Coverage는 94%로 측정되었다.

마무리

제목은 다소 거창하지만, 실제로 한 행동은 그렇게 어렵지는 않다. 다만 이 글에서 사용한 프로젝트는 순수 Kotlin 프로젝트로 안드로이드 프로젝트는 아니다.

이 면에서 보면, 안드로이드 프로젝트에 대해 유닛 테스트를 진행하는 것은 다소 어렵기 때문에 조금 진입장벽이 높을 수는 있다고는 판단된다.

하지만 작은 것 부터 나아가면, 점점 더 나아질 것이라 생각한다.