본문 바로가기

java

소나큐브(Sonarqube)와 jacoco로 코드품질 측정, 정적분석

Motivation

프로젝트에서 코드 퀄리티를 측정하고 유지하기 위해서 sonarqube를 적용했습니다.

추가적으로 테스트 코드를 얼마나 잘 작성하고 있는지, 즉 코드 커버리지 측정을 위해 jacoco도 함께 적용했습니다.

(대상이된 프로젝트는 java와 kotlin 언어로 이루어져 있습니다.)

소나큐브란 ?

소나큐브는 정적 프로그램 분석 도구입니다. 정적분석은 프로그램을 실행하지 않은 상태에서 소스 코드나 컴파일된 코드를 이용해 프로그램을 분석하는 방법입니다.

소나큐브는 아래 7가지 품질 요소를 기준으로 코드의 품질을 측정합니다.

  1. 버그(Reliability) : 잠재적인 버그, 런타임 중 예상되는 이슈
  2. 코드악취(Maintainability) : 심각한 이슈는 아니지만 사소한 이슈들. 모듈성, 이해가능성, 변경모듈성(modularity), 이해가능성(understandability), 변경 가능성 (changeability), 테스트 용의성(testability), 재사용성(reusability) 등이 포함된다.
  3. 취약점() : 해커들에게 잠재적인 약점이 될 수 있는 보안 상의 이슈를 의미. sql 인젝션, 크로스 사이트 스크립팅 등이 포함됨
  4. 중복 : 코드의 중복을 의미함
  5. 단위테스트(unit tests): 단위 테스트에 대한 코드 커버리지를 측정하고, 수행한 테스트의 성공/실패 정보를 제공함. → java기반 언어의 코드 커버리지를 체크하는 라이브러리 jacoco를 연동할 예정.
  6. 복잡도 : 순환 복잡도나 인지 복잡도 측정
  7. 사이즈 : 소스코드 사이즈와 관련된 다양한 지표를 제공 (코드 라인수 전체 라인수 구문, 함수 클래스 파일 디렉터리 주석 수 코멘트 비율)

적용

  1. 소나큐브 서버 구성
  2. 구성한 ip로 접속해 프로젝트 및 토큰 생성
  3. build.gradle 플러그인 설정
plugins {
    ...
    id("org.sonarqube") version "3.2.0"
}
 
allprojects {
    apply(plugin = "org.sonarqube")
    ...
}

4. build.gradle 스캐너 프로퍼티 설정

  • projectKey : 2번에서 생성했던 프로젝트의 이름
  • host.url : 소나큐브 서버 ip주소
  • login : 2번에서 생성했던 토큰
  • branch.name : 어떤 브랜치를 분석할 건지, 브랜치 이름
  • sourceEncoding : 소스 인코딩
  • java.binaries : 자바 바이너리 파일의 위치로, 해당 프로젝트에서는 java와 kotlin 두 가지를 사용했기 때문에 콤마로 이어서 두 가지 경로를 적어줬다.
  • 소나큐브 스캐너 프로퍼티 docs : https://docs.sonarqube.org/8.9/analysis/analysis-parameters/
sonarqube {
    properties {
        property("sonar.projectKey", "프로젝트 키")
        property("sonar.host.url", "소나큐브 서버 주소")
        property("sonar.login", "sonarqube 로그인 토큰")
        property("sonar.branch.name", "develop")
        property("sonar.sourceEncoding", "UTF-8")
        property("sonar.java.binaries", "$buildDir/classes/java/main,$buildDir/classes/kotlin/main")
    }
}

 

결과 화면

코드 커버리지

코드 커버리지는 테스트 케이스가 얼마나 충족되었는지를 나타내는 지표 중 하나 입니다. 테스트를 진행했을 때 “코드 자체가 얼마나 실행되었느냐"를 수치를 통해 확인할 수 있습니다.

구분(라인) 커버리지

대표적인 라인 커버리지에 대해서 설명하겠습니다.

라인 커버리지는 테스트 코드에서 코드 한 줄이 한 번 이상 실행된다면 충족됩니다.

 > fun test(int x) {
1>    println("start test")
2>    if (x > 1) {
3>        println("skip")
4>    }
 > }

위 함수 test에 대하여 x = 0인 케이스에 대해서만 단위 테스트 코드를 만들었다고 하면, x > 1 은 false이기 때문에 if문 내부의 println(”skip”)은 실행되지 않습니다.

즉, 1~4까지의 라인 중 3번 라인이 실행되지 않으므로 라인 커버리지는 75%입니다.

jacoco

java 코드의 커버리지를 체크하는 라이브러리로, 테스트 코드를 돌리고 그 커버리지 결과를 눈으로 보기 좋게 xml, csv 등과 같은 형태의 리포트로 생성합니다. 리포트를 소나큐브와 연결해서 소나큐브에서 볼 수 있습니다.

  • java와 kotlin 등 여러 언어의 소스가 섞여 있을 때도 설정 없이 사용 가능함
  • 라인 커버리지와 브랜치 커버리지를 제공
    • 라인(구문) 커버리지 : 코드 한 줄이 한 번 이상 실행된다면 충족됨
    • 브랜치(결정) 커버리지 : 모든 조건식이 true/false 값을 가지게 되면 충족됨

jacoco 적용

jacoco 관련 Task

jacocoTestReport

: 바이너리 커버리지 결과를 사람이 읽기 좋은 형태의 리포트로 저장합니다. html 파일로 생성해 사람이 쉽게 눈으로 확인할 수도 있고, SonarQube 등으로 연동하기 위해 xml, csv 같은 형태로도 리포트를 생성할 수 있습니다.

jacocoTestCoverageVerification

원하는 커버리지 기준을 만족하는지 확인해주는 task. (필요 없을 것 같다)

jacocoTaskExtension

jacoco플러그인은 자동으로 모든 test 타입의 task에 jacocoTaskExtension을 추가하고 test task에서 그 설정을 변경할 수 있음

https://docs.gradle.org/current/dsl/org.gradle.testing.jacoco.plugins.JacocoTaskExtension.html

// JacocoTaskExtension에서 설정된 기본 값 
tasks.getByName<Test>("test") {
    extensions.configure(JacocoTaskExtension::class) {
        isEnabled = true
        destinationFile = file("$buildDir/jacoco/$name.exec")
        includes = listOf()
        excludes = listOf()
        excludeClassLoaders = listOf()
        isIncludeNoLocationClasses = false
        sessionId = "<auto-generated value>"
        isDumpOnExit = true
        classDumpDir = null
        output = JacocoTaskExtension.Output.FILE
        address = "localhost"
        port = 6300
        isJmx = false
    }
}

build.gradle jacoco 설정

저는 위 태스크들 중 JacocoReport에 대해서만 설정해주었습니다.

tasks.withType(JacocoReport::class.java) {
    reports {
        html.required.set(false)
        xml.required.set(true)
        csv.required.set(false)
    }
}
 
tasks.withType<Test> {
    useJUnitPlatform()
    finalizedBy(tasks.jacocoTestReport) // test 이후 jacocoTestReport 실행 하도록 설정
}
sonarqube {
    properties {
        ...
        property("sonar.coveragePlugin", "jacoco")
        property("sonar.coverage.jacoco.xmlReportPaths", "$buildDir/reports/jacoco/test/jacocoTestReport.xml")
    }
}

jacoco 결과를 xml형태로 저장하고, 소나큐브에서 coverage.jacoco.xmlReportPaths 설정으로 해당 xml파일을 가져와 보여주도록 설정했습니다.

jacoco 결과 파일을 저장하는 위치를 따로 지정하지 않았기 때문에 xml파일은 default로 설정된 $buildDir/reports/jacoco/test/jacocoTestReport.xml 로 저장됩니다.

keywords

  • sonarqube / 소나큐브
  • java 코드품질 측정
  • kotlin 코드품질 측정
  • 코드 커버리지
  • code coverage

 

반응형