최근에 “TDD, 클린 코드 with Java” 라는 강의를 수강하고 테스트 코드의 중요성에 대해 생각이 많아졌다.
이 강의에선 코드의 유지보수와 리팩토링, 테스트 코드의 중요성에 대해 강조하고 있었다.
강사분이 해주신 말씀중에 이런 말씀이 있었다.
네이버와 같은 거대한 IT 기업에도 레거시 코드가 반드시 존재하고, 우리는 레거시 코드들을 리팩토링 해야하는데 이것은 마치 움직이는 자동차의 바퀴를 갈아끼는 것과도 같다.
기술들은 시간이 지날수록 변화하고, 요구사항이 추가 / 변경될 때마다 코드를 수정해야 하는데, 그 코드를 어떻게 하면 안전하게 수정할 것인가?
정답은 바로 ‘테스트 코드’이다.
테스트 코드를 기반으로 여러 상황에 대해 정의를 하면, 코드를 수정해도 테스트 코드만 돌려보면 변경된 코드가 문제가 없는지 한번에 파악이 가능하다.
테스트 코드를 잘 작성한다면 수정에 대해 두려움도 없어질 것이고, 더이상 배포하고 프로그램이 잘 작동하기를 기도하지 않아도 되는 것이다!
하지만, 현재 우리 조직은 개발할 때 테스트 코드를 거의 구현하지 않으며 요구사항이 변경되어 배포할 때마다 버그가 없기를 간절히 소망하고 있다..
이러한 이유로 코드 커버리지 툴을 도입하고 정적 분석 파이프라인을 구축해서 테스트 코드 작성을 독려해보고 싶어 구축하게 됐다.
현재 고맙게도 우리의 팀원 분들이 정적 분석 도구인 소나큐브를 설치해주셨고, Github에 연동해서 PR (Pull Request)를 보낼 때마다 정적 분석이 실행되고 있다.
하지만, 단지 PR된 신규 코드에 대해서만 정적 분석을 진행하고, 기존 코드를 분석하거나 master 브랜치 등 현재 실제 상용에서 서비스중인 브랜치에 대해서는 정적 분석이 진행되고 있지 않았다.
소나큐브는 설치되어 있지만, 우리 프로젝트 전체 코드의 현상황에 대해 정적 분석을 돌려볼 수 있는 방법이 부재했고 이 부분이 아쉽게 느껴졌다.
또한, 테스트 코드 커버리지 툴이 적용되지 않아 코드 커버리지 지표를 확인할 수 없고, 항상 0.0%로 나오니까 팀원들 모두 경각심을 가지지 못하는 상황이다…

첫 번째로 코드 커버리지를 확인하기 위해 코드 커버리지 툴을 도입하기로 했다.
코드 커버리지 툴을 도입해서 코드 커버리지가 수치로 표현되면 다같이 테스트 코드를 많이 구현하지 않을까 하는 조심스러운 바램이다.
두 번째는 매일 모든 프로젝트에 대해 master 브랜치의 정적 분석 결과를 리포트 해주는 것이다.
현재 상용에서 서비스되고 있는 master 브랜치의 코드가 어떤지 매일 확인하면서 변화를 확인할 수도 있고, 취약점이나 개선점이 발견되면 미리 조치할 수 있는 이점이 있을 것이다.
또한, 질 좋은 코드를 더욱 장려할 수 있을 것 같았다!
현재 우리는 자바와 소나큐브를 사용하고 있다. 소나 큐브에서는 코드 커버리지 리포트를 직접 생성하지 않지만, 자바 프로젝트에 대해 JaCoCo를 직접 지원한다고 공식 문서에 나와 있다.

또한, 다른 코드 커버리지 툴보다 래퍼런스가 많아서 코드 커버리지 툴로 JaCoCo를 채용하게 됐다.
프로젝트에 JaCoCo를 적용하는 방법은 다음과 같다. (참조)
먼저, gradle에 플러그인을 추가해준다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.x.x'
...
id 'jacoco'
}
다음으로, JaCoCo는 테스트를 돌리고 그 결과를 기준으로 리포트 해주기 때문에 test 이후에 작업을 설정해준다.
tasks.named('test') {
useJUnitPlatform()
finalizedBy 'jacocoTestReport'
}
jacocoTestReport {
reports {
html.enabled true
csv.enabled true
xml.enabled true
}
finalizedBy 'jacocoTestCoverageVerification'
}
커버리지 리포트 결과로 어떤 형식의 파일들을 생성할 지 지정할 수 있는데, xml은 소나큐브 분석에 사용되고 csv와 html은 JaCoCo 결과를 눈으로 확인하기 위해 생성설정을 해뒀다.
그리고 다음에 어떤 방식으로 커버리지를 체크할 지 설정해준다.
jacocoTestCoverageVerification {
violationRules {
rule {
enabled = true
element = 'CLASS'
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.50
}
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.50
}
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 300
}
}
}
}
rule은 여러개를 커스텀해서 설정할 수 있다.
먼저 커버리지 체크 기준이 되는 element를 설정해준다.
element에는 BUNDLE, CLASS, GROUP, METHOD, PACKAGE, SOURCEFILE 이 있는데, (참조) 우리는 클래스 단위로 브랜치와 라인 커버리지를 체크한다고 설정했다. (일반적으로 초기에는 너무 추상적이지도, 세부하지도 않은 CLASS로 한다고 한다.)
counter에는 BRANCH, CLASS, COMPLEXITY, INSTRUCTION, LINE, METHOD를 설정할 수 있는데, (참조) 현재 테스트코드가 거의 전무한 상태이므로 LINE, BRANCH를 낮게 설정했다.
이제 커버리지 제외할 클래스를 넣어야하는데, Querydsl을 사용하므로 Q클래스들은 코드 커버리지에서 제외 시키는 로직을 추가해준다.
jacocoTestCoverageVerification {
def qTypes = []
for (qPattern in '*.entity.QA'..'*.entity.QZ'){
qTypes.add(qPattern + '*')
}
violationRules {
rule {
...
excludes = ["*.*Application"] + qTypes
}
}
클래스 패턴을 통해 자동 생성된 Q 클래스들을 커버리지에서 제외할 수 있다.
리포트 결과를 연동하는 것은 gradle일 경우, 소나큐브가 알아서 찾아서 리포트 결과를 가져오므로, 별도의 설정을 하지 않아도 된다.

정적 분석 자동화 파이프라인은 Jenkins를 통해 구축했다.
먼저 Parameter로 String을 받아서 해당 branch를 분석할 수 있도록 설정해줬다.

여기에 빌드 트리거를 설정해서 매일 지정한 시간(새벽 6시)에 정적분석을 동작하도록 했다.

마지막으로 Pipeline script로 파이프라인을 구축했다.
pipeline {
agent any
environment {
JAVA_HOME = '/path/to/java'
PATH = "${env.JAVA_HOME}/bin:${env.PATH}"
}
stages {
stage('Checkout') {
steps {
script {
git branch: "$BRANCH", credentialsId: 'your-credential-id', url: 'your-github-url'
}
}
}
stage('Build') {
steps {
script {
try {
sh '''
chmod +x ./gradlew
./gradlew clean
./gradlew build
'''
} catch(e) {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE'){
sh "exit 0"
echo 'Test Code failed'
}
}
}
}
}
stage('SonarQube analysis') {
steps {
script {
sh """
./gradlew sonar \
-Dsonar.token=your_sonar_token \
-Dsonar.projectKey=your_project_key \
-Dsonar.projectName=your_project_name \
-Dsonar.host.url=your_sonar_host_url \
-Dsonar.sources=src/main/java \
-Dsonar.java.binaries=build/classes/java/main
"""
}
}
}
}
}
build 스테이지에서 try-catch 구문을 확인할 수 있는데, JaCoCo의 지정한 커버리지 룰에 도달하지 못할 시 빌드가 실패하므로 다음 단계로 넘어가지 않는 문제점이 있어 추가했다.

기존에 0.0%로 뜨던 Coverage 지표가 테스트 코드를 인식해서 조금 오른 것을 확인할 수 있다.
확실히 테스트코드를 평소에 구현하지 않았구나 하고 반성하게 됐고, 앞으로 경각심을 가지고 테스트 코드 작성에 힘써야할 것 같다.!