Developer Kit/SonarQube

Project에 SonarQube 적용하기

시나민 2023. 2. 14. 20:40

SonarQube

20개 이상의 프로그래밍 언어에서 버그, 코드 스멜, 보안 취약점을 발견할 목적으로,

정적 코드 분석 자동 리뷰를 수행하기 위한 지속적 코드 품질 검사용 오픈 소스 플랫폼

⭐ 동적 코드 분석 : Test Code를 작성하여 런타임 환경에서 코드를 분석하는 방법

⭐ 정적 코드 분석 : 소스 코드의 실행 없이 비 런타임 환경에서 코드를 분석하는 방법

⭐ 코드 스멜(code smell) : 소스 코드에서 문제를 일으킬 가능성이 농후한 코드

- 잠재적으로 버그가 발생할 수 있는 코드
- 안티 패턴
- 코드 스타일(컨벤션) 위반 여부
- 성능 문제
- 오타
- 사용되지 않는 코드
- 잠재적인 보안 취약점

정적 코드분석을 왜 하는가?

  1. 코드가 완성되지 않았다고 하더라도 분석이 가능하다.
  2. - 동적 분석을 하기 위해서는 Test Code가 작성되어야 하므로, 좀 더 이른 시점에서 문제를 잡아낼 수 있다. - 코드가 늘어날 수록 유지보수 해야하는 Test Code 또한 늘어지만, 정적 분석에서는 제약이 없다.
  3. 여러 사람이 참여하는 프로젝트에서 코드 스타일을 통일하는데에 큰 도움이 될 수 있다.

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?

이것이 왜 문제가 되는지에 대해 설명된다.