[I-project] 소나클라우드를 통해 pr 정적 코드 분석(with jacoco) && spotless 검사 자동화

Jifrozen·2023년 12월 23일
0

I-project

목록 보기
2/4

SonarCloud란?

소스 코드 품질을 관리하고 개선할 수 있도록 돕는 클라우드 기반의 정적 분석 서비스
소나큐브를 프로젝트에 적용해본 경험이 있는데 서버 올리기도 귀찮고 소나클라우드도 자주쓴다고 하여 이번 프로젝트에는 소나 클라우드를 적용해보았다.

프로젝트에 SonarCloud 적용

  1. 소나클라우드 웹사이트 가입, Github 연동
    처음 나의 github repo계정이 member로 되어있어 권한이 없어 연동이 안되었다. 이는 webhook부분에서 권한을 따로 얻거나 권한을 바꿔줘야한다.

    캡처를 진행하지 않아 참고링크에 있는 사진을 들고왔다. 정적코드 분석하고자하는 repo를 선택한다.

  2. map과 key를 얻을수있는데 민감한정보는 아니고 build.gradle에 설정하고자 하는 정보이다.

    위와 같은 대시보드를 확인할 수 있다.

  3. Automatic Analysis 끄기
    github actions를 통해 ci 분석을 진행하기 때문에 자동분석은 끈다.

  4. github actions 튜토리얼 진행

    소나클라우드 내에서 github actions 튜토리얼을 제공한다. 이를 클릭하면 SONAR_TOKEN을 얻을 수 있다. 이는 actions가 트리커를 통해 돌때 리포트를 소나클라우드로 제공하기 위한 토큰이다.
    깃허브 repo에 들어가 시크릿 변수를 등록한다.

  1. build.gradle을 통해 jacoco와 sonar 속성값 지정

jacoco란?

테스트 커버리지 데이터를 수집도구, SonarCloud와 통합하여 사용할 수 있다.

//플러그인 설치 설정
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'org.asciidoctor.jvm.convert' version '3.3.2'
    // 정적코드분석을 위한 추가
    id 'com.diffplug.spotless' version '6.11.0'
    id 'jacoco'
    id "org.sonarqube" version "4.4.1.3373"
}
//test 태스크 블록 junit 플랫폼 기반 테스트 진행하고
// 테스트가 끝나면 jacoco 리포트를 생성
test{
    useJUnitPlatform()
    finalizedBy 'jacocoTestReport'
}
// Jacoco 리포트 파일들을 저장할 디렉토리
def jacocoDir = layout.buildDirectory.dir("reports/")
//특정 클래스 파일을 Jacoco 리포트에서 제외
private excludedClassFilesForReport(classDirectories) {
    classDirectories.setFrom(
            files(classDirectories.files.collect {
                fileTree(dir: it, exclude: [
                        "**/Q*",
                        "**/*DTO*",
                        "**/*Code*",
                        "**/*Exception*",
                        "**/*Develop*",
                        "**/*Application*",
                        "**/*Config*"
                ])
            })
    )
}
// 소나 클라우드 설정
sonar {
    properties {
        property "sonar.projectKey", "Compono-Team_I-backend"
        property "sonar.organization", "compono-team"
        property "sonar.host.url", "https://sonarcloud.io"
        property 'sonar.sources', 'src'
        property 'sonar.language', 'java'
        property 'sonar.sourceEncoding', 'UTF-8'
        property 'sonar.exclusions', '**/test/**, **/Q*.java, **/*Doc*.java, **/resources/** ,**/*Application*.java , **/*Config*.java,' +
                '**/*Dto*.java,**/*DTO*.java, **/*Request*.java, **/*Response*.java ,**/*Exception*.java ,**/*ErrorCode*.java'
        property 'sonar.test.inclusions', '**/*Test.java'
        property 'sonar.java.coveragePlugin', 'jacoco'
        property 'sonar.coverage.jacoco.xmlReportPaths', jacocoDir.get().file("jacoco/index.xml").asFile
    }
}
// 자코코 버전 선택
jacoco {
    toolVersion = "0.8.8"
}
//테스트 리포트 생성 설정
jacocoTestReport {
    dependsOn test
    reports {
        html.required.set(true)
        xml.required.set(true)
        csv.required.set(true)
        html.destination jacocoDir.get().file("jacoco/index.html").asFile
        xml.destination jacocoDir.get().file("jacoco/index.xml").asFile
        csv.destination jacocoDir.get().file("jacoco/index.csv").asFile
    }

    excludedClassFilesForReport(classDirectories)

    finalizedBy 'jacocoTestCoverageVerification'
}
//테스트 커버리지 규칙 정의하고 검증 
jacocoTestCoverageVerification {
    excludedClassFilesForReport(classDirectories)

    violationRules {
        rule {
            // rule 활성화
            enabled = true

            // 클래스 단위로 룰 체크
            element = 'CLASS'

            // 라인 커버리지를 최소 70% 만족
            limit {
                counter = 'LINE'
                value = 'COVEREDRATIO'
                minimum = 0.70
            }

//            excludes = jacocoExcludePatterns
        }
    }
}
//코드 포맷팅 규칙 정의
spotless {
    java {
        target("**/*.java")
        importOrder() // import 순서 정의
        removeUnusedImports() // 사용하지 않는 import 제거
        trimTrailingWhitespace() // 공백 제거
        endWithNewline() // 끝부분 NewLine 처리
        googleJavaFormat().aosp() // google java format
    }
}

// pre-commit spotless check script
//커밋 전에 코드 컨벤션이 잘못된게 있는지 검사
tasks.register('updateGitHooks', Copy) {
    from './scripts/pre-commit'
    into './.git/hooks'
    fileMode 0775
}
compileJava.dependsOn updateGitHooks
  1. spotless pre-comit 파일 생성
    Spotless는 코드 포맷팅을 자동화하는 도구로, pre-commit 훅과 함께 사용하여 코드가 커밋되기 전에 자동으로 포맷팅되도록 할 수 있습니다:
    ./scripts 폴더안에 생성한다.
#!/bin/sh
# Part 1 스테이지에 올라간 파일 가져오기
stagedFiles=$(git diff --staged --name-only)
# Part 2 코드 컨벤션 적용 명령어
echo "Running spotlessApply. Formatting code..."
./gradlew spotlessApply --daemon
# Part 3 스테이지에 올라간 파일중 코드 컨벤션 적용으로 인해 수정된 파일이 있으면 다시 git add
for file in $stagedFiles; do
  if test -f "$file"; then
    git add "$file"
  fi
done

pre-commit 스크립트

  1. git actions 추가
name: ci
on:
  pull_request:
    branches:
      - develop
jobs:
  build:
    name: Build and analyze
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17
          distribution: 'adopt'

      - name: Cache SonarCloud packages
        uses: actions/cache@v3
        with:
          path: ~/.sonar/cache
          key: ${{ runner.os }}-sonar
          restore-keys: ${{ runner.os }}-sonar

      - name: Cache Gradle packages
        uses: actions/cache@v3
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: ${{ runner.os }}-gradle

      - name: Build and analyze
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: ./gradlew sonar --info --stacktrace

참고자료

https://gong-check.github.io/dev-blog/BE/오리/sonarcloud/sonarcloud/
https://docs.sonarsource.com/sonarcloud/improving/new-code-definition/
https://medium.com/@mmessell/apply-spotless-formatting-with-git-pre-commit-hook-1c484ea68c34
https://nesoy.github.io/articles/2020-04/Spotless
https://velog.io/@chaerim1001/Springboot-프로젝트-SonarCloud로-정적-코드-분석하기-with-github-actions
https://devnm.tistory.com/36

0개의 댓글