Motivation
프로젝트에서 코드 퀄리티를 측정하고 유지하기 위해서 sonarqube를 적용했습니다.
추가적으로 테스트 코드를 얼마나 잘 작성하고 있는지, 즉 코드 커버리지 측정을 위해 jacoco도 함께 적용했습니다.
(대상이된 프로젝트는 java와 kotlin 언어로 이루어져 있습니다.)
소나큐브란 ?
소나큐브는 정적 프로그램 분석 도구입니다. 정적분석은 프로그램을 실행하지 않은 상태에서 소스 코드나 컴파일된 코드를 이용해 프로그램을 분석하는 방법입니다.
소나큐브는 아래 7가지 품질 요소를 기준으로 코드의 품질을 측정합니다.
- 버그(Reliability) : 잠재적인 버그, 런타임 중 예상되는 이슈
- 코드악취(Maintainability) : 심각한 이슈는 아니지만 사소한 이슈들. 모듈성, 이해가능성, 변경모듈성(modularity), 이해가능성(understandability), 변경 가능성 (changeability), 테스트 용의성(testability), 재사용성(reusability) 등이 포함된다.
- 취약점() : 해커들에게 잠재적인 약점이 될 수 있는 보안 상의 이슈를 의미. sql 인젝션, 크로스 사이트 스크립팅 등이 포함됨
- 중복 : 코드의 중복을 의미함
- 단위테스트(unit tests): 단위 테스트에 대한 코드 커버리지를 측정하고, 수행한 테스트의 성공/실패 정보를 제공함. → java기반 언어의 코드 커버리지를 체크하는 라이브러리 jacoco를 연동할 예정.
- 복잡도 : 순환 복잡도나 인지 복잡도 측정
- 사이즈 : 소스코드 사이즈와 관련된 다양한 지표를 제공 (코드 라인수 전체 라인수 구문, 함수 클래스 파일 디렉터리 주석 수 코멘트 비율)
적용
- 소나큐브 서버 구성
- 구성한 ip로 접속해 프로젝트 및 토큰 생성
- 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
'java' 카테고리의 다른 글
Java 환경변수 설정이 필요한 이유, 설정 방법 (mac) (0) | 2023.02.02 |
---|---|
[java] File 지우기. delete vs deleteOnExit() (0) | 2022.06.22 |
[Java] Builder Pattern (0) | 2022.01.05 |
[Java] Nested Class (중첩 클래스) (0) | 2022.01.05 |