Project에 SonarQube 적용하기
SonarQube
20개 이상의 프로그래밍 언어에서 버그, 코드 스멜, 보안 취약점을 발견할 목적으로,
정적 코드 분석 자동 리뷰를 수행하기 위한 지속적 코드 품질 검사용 오픈 소스 플랫폼
⭐ 동적 코드 분석 : Test Code를 작성하여 런타임 환경에서 코드를 분석하는 방법
⭐ 정적 코드 분석 : 소스 코드의 실행 없이 비 런타임 환경에서 코드를 분석하는 방법
⭐ 코드 스멜(code smell) : 소스 코드에서 문제를 일으킬 가능성이 농후한 코드
- 잠재적으로 버그가 발생할 수 있는 코드
- 안티 패턴
- 코드 스타일(컨벤션) 위반 여부
- 성능 문제
- 오타
- 사용되지 않는 코드
- 잠재적인 보안 취약점
정적 코드분석을 왜 하는가?
- 코드가 완성되지 않았다고 하더라도 분석이 가능하다.
- - 동적 분석을 하기 위해서는 Test Code가 작성되어야 하므로, 좀 더 이른 시점에서 문제를 잡아낼 수 있다. - 코드가 늘어날 수록 유지보수 해야하는 Test Code 또한 늘어지만, 정적 분석에서는 제약이 없다.
- 여러 사람이 참여하는 프로젝트에서 코드 스타일을 통일하는데에 큰 도움이 될 수 있다.
JACOCO
Jacoco란 gradle에서 기본으로 제공하는 Java Code Coverage 툴 중에 하나로, 프로젝트의 코드 커버리지를 계산해준다.
SonarQube 설치
참고 사이트 : https://jeeneee.dev/springboot/springboot-2-introduction-of-sonarqube/
Docker를 사용하여 설치
1. Doker를 실행 후 다음 명령어를 입력한다.
$ docker run -d --name sonarqube -p 9000:9000 sonarqube
⭐ docker run : 이미지를 컨테이너로 만들어 실행하는 명령어
⭐ -d : Demon Mode로(백그라운드에서) 실행
⭐ --name : 컨테이너 이름 설정
⭐ -p host port : container port : 포트 설정
2. http://localhost:9000 으로 접속하여 Login
SonarQube 초기 ID / Password는 각각 admin / admin이다.
로그인하게되면 비밀번호 변경창이 나오게된다.
3. SonarQube Token 생성.
3-1. 상단 메뉴바에 Administration에 들어간다.
3-2. Secrurity -> Users 를 클릭하고 우측에 Update Tokens를 클릭한다.
3-3. Token Name을 입력하고 유효기간 설정 후 Generate를 눌러 토큰을 생성한다.
Token 값은 현재 창 이후에는 다시 확인할 수 없기 때문에, 따로 저장을 한다.
Token 값 분실 시, 토큰을 재 생성한다.
build.gradle 설정
현재 기술한 방법은 SonarScanner를 통해 코드를 분석하는 방법이 아닌,
Jacoco로 계산한 파일을 SonarQube에서 분석하는 방법이다.
코드 전문
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id "org.asciidoctor.jvm.convert" version "3.3.2"
//SonarQube plugin 추가
id "org.sonarqube" version "3.5.0.2730"
//jacoco (Java Code Coverage) plugin 추가 / jacoco : 프로젝트의 코드 커버리지 계산해주는 plug in
id 'jacoco'
}
test {
useJUnitPlatform()
//현재 동작이 수행된 다음 finalizedBy에 등록된 동작을 수행하겠다.
finalizedBy 'jacocoTestReport'
}
jacoco {
toolVersion = "0.8.7"
}
//report 산출 포맷 / SonarQube 최신 버전은 xml 파일만 읽도록 설정되어있기 때문에 xml로 출력
jacocoTestReport {
reports {
xml.enabled true
csv.enabled false
html.enabled false
//현재 동작이 수행된 다음 finalizedBy에 등록된 동작을 수행하겠다.
finalizedBy 'jacocoTestCoverageVerification'
}
}
jacocoTestCoverageVerification{
violationRules {
rule {
//rule 활성 여부
enabled = true
//측정의 큰 단위
element = 'CLASS'
//rule 상세 설정
limit {
//coverage 측정 최소 단위
counter = 'LINE'
value = 'COVEREDRATIO'
//60%이하는 통과시키지 않겠다.
minimum = 0.60
}
}
}
}
sonarqube {
properties {
property "sonar.host.url", "http://localhost:9000"
//로그인을 위한 token 데이터 입력
property "sonar.login", "squ_f38e07ce6b69aa7bed4a090acfa44b97834cb701"
property "sonar.sources", "src"
property "sonar.language", "java"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.profile", "Sonar way"
property "sonar.test.inclusions", "**/*Test.java"
//Jacoco로 출력한 xml 파일 경로
property 'sonar.coverage.jacoco.xmlReportPaths', "${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
}
}
Plug In
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id "org.asciidoctor.jvm.convert" version "3.3.2"
//SonarQube plugin 추가
id "org.sonarqube" version "3.5.0.2730"
//jacoco (Java Code Coverage) plugin 추가 / jacoco : 프로젝트의 코드 커버리지 계산해주는 plug in
id 'jacoco'
}
SonarQube와 Jacoco Plug In을 추가한다.
Jacoco 설정
test {
useJUnitPlatform()
//현재 동작이 수행된 다음 finalizedBy에 등록된 동작을 수행하겠다.
finalizedBy 'jacocoTestReport'
}
jacoco {
toolVersion = "0.8.7"
}
//report 산출 포맷 / SonarQube 최신 버전은 xml 파일만 읽도록 설정되어있기 때문에 xml로 출력
jacocoTestReport {
reports {
xml.enabled true
csv.enabled false
html.enabled false
//현재 동작이 수행된 다음 finalizedBy에 등록된 동작을 수행하겠다.
finalizedBy 'jacocoTestCoverageVerification'
}
}
jacocoTestCoverageVerification{
violationRules {
rule {
//rule 활성 여부
enabled = true
//측정의 큰 단위
element = 'CLASS'
//rule 상세 설정
limit {
//coverage 측정 최소 단위
counter = 'LINE'
value = 'COVEREDRATIO'
//60%이하는 통과시키지 않겠다.
minimum = 0.60
}
}
}
}
⭐ test : 테스트 코드를 실행
⭐ finalizedBy : 현재 로직을 수행 한 후 다음 수행할 로직을 설정하는 명령어
⭐ jacocoTestReport : 분석한 결과물을 설정한 포맷으로 산출
- SonarQube 최신 버전에서는 xml 파일만 읽도록 설정되어 있기 때문에, xml.enabled를 true로 설정
⭐ jacocoTestCoverageVerification : 결과값을 설정한 규칙에 따라 정의
SonarQube 설정
sonarqube {
properties {
property "sonar.host.url", "http://localhost:9000"
//로그인을 위한 token 데이터 입력
property "sonar.login", "squ_f38e07ce6b69aa7bed4a090acfa44b97834cb701"
property "sonar.sources", "src"
property "sonar.language", "java"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.profile", "Sonar way"
property "sonar.test.inclusions", "**/*Test.java"
//Jacoco로 출력한 xml 파일 경로
property 'sonar.coverage.jacoco.xmlReportPaths', "${buildDir}/reports/jacoco/test/jacocoTestReport.xml"
}
}
⭐ property "sonar.login" : 아까 생성한 토큰을 사용하여 SonarQube에 적용
- 현재 로그인된 아이디의 토큰과 다를 경우,
Not authorized. Please check the properties sonar.login and sonar.password 오류가 발생한다.
테스트 실행
IntelliJ Console창에 명령어 입력
./gradlew test sonarqube
빌드가 실패한다.
실패 이유를 찾기 위해오류를 확인해보자.
Task :jacocoTestCoverageVerification FAILED
jacocoTestCoverageVerification 에서 에러가 발생했는데,
우리가 위에서 설정한 Code Coverage limit의 minimum 값이 0.60, 즉 60%로 설정되었는데,
프로젝트 내 코드들의 coverage가 60%에 미치지 못 하기 때문에 빌드가 실패한다.
테스트를 위해 해당 부분을 주석 처리 후 다시 빌드를 진행한다.
Coverage의 제한을 없앴기 때문에 Bulid가 성공하고, 결과 값이 Localhost:9000으로 전송되었다.
SonarQube에서 테스트 결과 확인
LocalHost:9000 Projects에 Local에서 진행한 프로젝트가 전송되어있음을 확인할 수 있다.
Bug를 선택한다.
⭐ Where is the Isuue?
어디서 오류가 발생했는지 아래에 빨간물결표로 표시가 된다.
⭐ Why is this an issue?
이것이 왜 문제가 되는지에 대해 설명된다.