MSA 인프라 구축하기 with Azure(6)-jacoco & Sonarqube

SeungJu(하늘하늘)·2022년 10월 12일
0

1. Jacoco

jacoco란 Java의 코드 커버리지를 측정하는 라이브러리입니다. 테스트 코드의 결과를 html과 xml, csv같은 리포트로 생성합니다.

먼저 jacoco plugin을 추가하고 셋팅합니다.

Maven

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<properties>
		<jacoco.version>0.8.5</jacoco.version>
		...
	</properties>
	
	<build>
		<plugins>
		    ...
			<plugin>
				<groupId>org.jacoco</groupId>
				<artifactId>jacoco-maven-plugin</artifactId>
				<version>${jacoco.version}</version>
				<executions>
				
					<!-- JaCoCo Runtime Agent에 대한 property를 준비 -->
					<execution>
						<id>prepare-agent</id>
						<goals>
							<goal>prepare-agent</goal>
						</goals>
					</execution>
					
					<!-- code coverage 리포트를 생성 -->
					<execution>
						<id>report</id>
						<phase>prepare-package</phase>
						<goals>
							<goal>report</goal>
						</goals>
					</execution>
					
					<!-- code coverage metric이 충돌하는지 확인 -->
					<execution>
						<id>check</id>
						<goals>
							<goal>check</goal>
						</goals>
						<configuration>
							<rules>
								<rule>
									<element>BUNDLE</element>
									<limits>
										<limit>
											<!-- 빈 줄을 제외한 실제 코드의 라인 수 -->
											<counter>LINE</counter>
											<!-- 커버된 비율 -->
											<value>COVEREDRATIO</value>
											<minimum>0.80</minimum>
										</limit>
										<limit>
											<!-- 조건문 등의 분기 수 -->
											<counter>BRANCH</counter>
											<!-- 커버된 비율 -->
											<value>COVEREDRATIO</value>
											<minimum>0.80</minimum>
										</limit>
									</limits>
									
									<!-- 테스트 커버리지 측정에서 제거하는 클래스들 -->
									<excludes>
										<exclude>com/mungta/accusation/*Application.class</exclude>
										<exclude>com/mungta/accusation/config/*</exclude>
										<exclude>com/mungta/accusation/messagequeue/*</exclude>
										...
									</excludes>
								</rule>
							</rules>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>
Gradle

build.gradle

plugins {
	...
    id 'jacoco' //jacoco plugin 추가
    ...
}
jacoco {
    toolVersion = '0.8.5'
    reportsDir = file("$buildDir/jacoco") // 리포트 파일 위치
}
jacocoTestReport {
    reports {
    	// 사용할 리포트 형식(csv도 추가할 수 있음)
        html.enabled true
        xml.enabled true
		// 리포트 형식 별 위치
        html.destination file("$buildDir/jacoco/jacoco.html")
        xml.destination file("$buildDir/jacoco/jacoco.xml")
    }
    afterEvaluate {
    //CodeCoverage 측정에서 제거하는 클래스들 1
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it,
                    exclude: [
                            "com/mungta/questionsservice/QuestionsServiceApplication.class",
                            "com/mungta/questionsservice/common/**",
                            "com/mungta/questionsservice/config/**",
                            "com/mungta/questionsservice/domain/BaseEntity.class",
                            "com/mungta/questionsservice/domain/question/dto/**",
                            "com/mungta/questionsservice/domain/response/dto/**"
                    ])
        }))
    }
    finalizedBy 'jacocoTestCoverageVerification' // jacocoTestReport 테스크 끝난 후에 jacocoTestCoverageVerification 테스크 실행
}
test.finalizedBy 'jacocoTestReport' //테스트 테스크 끝난 후에 jacocoTestReport Task 실행

jacocoTestCoverageVerification {
//	룰 설정
    violationRules {
        rule {
            element = 'CLASS'
            enabled = true
			//라인 Cover 비율 80%이상인 경우만 통과
            limit {
                counter = 'LINE'
                value = 'COVEREDRATIO'
                minimum = 0.80
            }
			//메서드 Cover 비율 80%이상인 경우만 통과
            limit {
                counter = 'METHOD'
                value = 'COVEREDRATIO'
                minimum = 0.80
            }
            //CodeCoverage 측정에 포함하는 클래스들
            includes = [
                    '**/domain/**'
            ]
            //CodeCoverage 측정에 제거하는 클래스들 2
//            excludes = [
//                    "com/mungta/questionsservice/QuestionsServiceApplication.class",
//                    "com/mungta/questionsservice/common/**",
//                    "com/mungta/questionsservice/config/**",
//                    "com/mungta/questionsservice/domain/BaseEntity.class",
//                    "com/mungta/questionsservice/domain/question/dto/**",
//                    "com/mungta/questionsservice/domain/response/dto/**"
//            ]
        }
    }
}
test {
    useJUnitPlatform()
    //CodeCoverage 측정에서 제거하는 클래스들 3
//    jacoco {
//        excludes += [
//                "com/mungta/questionsservice/QuestionsServiceApplication.class",
//                "com/mungta/questionsservice/common/**",
//                "com/mungta/questionsservice/config/**",
//                "com/mungta/questionsservice/domain/BaseEntity.class",
//                "com/mungta/questionsservice/domain/question/dto/**",
//                "com/mungta/questionsservice/domain/response/dto/**"
//        ]
//    }
}

그런데..솔직히 Gradle은 좀 문제가 있었습니다. 테스트 코드에 config나 객체와 같은 코드는 테스트가 굳이 필요 없으므로 코드 커버리지 측정에서 제외되어야합니다. 이를 위한 방법은 크게 3가지로 위에 주석으로 달아 둔 [ CodeCoverage 측정에서 제거하는 클래스들 1,2,3] 입니다. 그리고 gradle버전이 올라감에 따라 그 중에서도 1번과 같은 방법으로 해야한다고 합니다. 하지만 아무리 해도 해당 클래스들이 exclude가 되지 않았습니다.. 그래서 생각한 꼼수(?)가 역으로 include로 필요한 부분만 넣는 것이었습니다. 만약 혹시 이에 대한 해결방법을 아시는 분이 있다면 댓글로 꼭 남겨주시면 감사하겠습니다.

이렇게 설정을 마치고 나면 maven/gradle test를 진행할 때에 jacoco에서 report를 남겨줍니다. xml리포트는 sonarqube에서 참고하므로 sonarqube의 codeCoverage를 확인하려면 xml리포트를 남기도록 반드시 설정이 필요합니다.

2. Sonarqube

sonarqube는 Jenkins서버에서 docker로 설치할 예정입니다. 어차피 Jenkins 빌드에 Docker도 필요하므로 jenkins에 접속해서 docker를 먼저 설치하겠습니다.

vm에 docker 설치

먼저 ssh로 jenkins 서버에 접속합니다.
해당 변수들은 앞에서 vm을 만들때 확인하였던 값들로 대체하시면 됩니다.

ssh ${adminId}@${VM IP}

다음 docker를 설치합니다.

sudo wget -qO- http://get.docker.com/ | sh
docker로 sonarqube 설치

linux ulimit를 설정합니다.

$ sudo vi /etc/sysctl.conf
# 아래 내용 추가
vm.max_map_count=262144
fs.file-max=65536

$ sudo vi /etc/security/limits.conf
# 아래 내용 추가
sonar    -    nofile    65536
sonar    -    nproc    4096

$ sudo sysctl -p

docker bridge network를 생성합니다.

$ docker network create --driver bridge sonarqube-net
$ docker network ls

sonarqube를 위한 디렉토리를 생성합니다.

$ sudo mkdir /data
$ cd /data
$ sudo mkdir sonarqube
$ cd sonarqube

$ sudo chown -R $USER:$USER /data/sonarqube/
$ sudo chmod -R 755 /data/sonarqube/

docker-compose.yml을 생성합니다.

version: '3.8'

services:

  sonarqube:
    image: sonarqube:8.9.2-community
    container_name: sonarqube
    restart: always
    depends_on:
      - db
    ports:
      - '9000:9000'
    environment:
      SONARQUBE_HOME: '/opt/sonarqube'
      SONARQUBE_JDBC_URL: 'jdbc:postgresql://db:5432/sonar'
      SONARQUBE_JDBC_USERNAME: sonar
      SONARQUBE_JDBC_PASSWORD: sonar
      TZ: 'Asia/Seoul'
    volumes:
      - 'sonarqube_conf:/opt/sonarqube/conf'
      - 'sonarqube_data:/opt/sonarqube/data'
      - 'sonarqube_logs:/opt/sonarqube/logs'
      - 'sonarqube_extensions:/opt/sonarqube/extensions'

  db:
    image: postgres:13.4
    container_name: postgresql
    restart: always
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - 'postgresql:/var/lib/postgresql'
      - 'postgresql_data:/var/lib/postgresql/data'

networks:
  default:
    external:
      name: sonarqube-net

volumes:
  sonarqube_conf:
  sonarqube_data:
  sonarqube_logs:
  sonarqube_extensions:
  postgresql:
  postgresql_data:

docker compose를 설치합니다.

$ sudo curl \
    -L "https://github.com/docker/compose/releases/download/v2.7.0/docker-compose-linux-x86_64" \
    -o /usr/local/bin/docker-compose

docker compose를 이용해서 sonarqube를 구동합니다.

$ docker-compose up -d

# 네트워크에 두개의 컨테이너가 연결되었는지 확인
$ docker network inspect sonarqube-net

# 로그 확인
$ docker-compose logs -f

# 컨테이너 목록 확인
$ docker-compose ps -a

Azure VM에 네트워크 탭에서 인바운드 포트 규칙을 추가합니다.

sonarqube 셋팅

sonarqube에 접속합니다.(http://${ip}:9000)
기본 ID/PW 는 admin/admin입니다.

우측 상단에 A를 클릭하고 My Account로 들어갑니다.

Security탭으로 들어가서 토큰 이름을 입력 후 Generate를 클릭합니다.

해당 토큰은 다시 확인할 수 없으므로 토큰 값은 어디에 기록해두기를 추천드립니다.

Jenkins와 Sonarqube 연결

Jenkins에 접속합니다.
Dashboard > Jenkins 관리 > System Configuration > 플러그인 관리 > 설치 가능 클릭합니다.
config를 검색해서 Config File Provider 체크 후 install without restart를 클릭합니다.

Dashboard > Jenkins 관리 > System Configuration > Managed files에 들어간 후 "Add a new Config"를 클릭합니다.
Global Maven settings.xml 체크, ID: maven-settings 입력 > Next 클릭

Content에 아래 내용 추가 > Submit를 클릭합니다.

  • ${sonarqube_ip} : sonarqube ip
  • ${auth_token} : 발급한 토큰
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <pluginGroups>
        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
    </pluginGroups>
    <profiles>
        <profile>
            <id>sonar</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <sonar.host.url>http://${sonarqube_ip}:9000</sonar.host.url>
                <sonar.login>${auth_token}</sonar.login>
            </properties>
        </profile>
    </profiles>
</settings>
Gradle

gradle은 파일에 직접 설정했었습니다. 위의 Maven과 같이 젠킨스에 설정할 수도 있겠지만 그렇게 하기보다 직접 로컬에서도 테스팅을 해보려 했었기 때문에 직접 파일에 설정하게 되었습니다.

  • 변수는 맞게 직접 설정해주시면 됩니다.

build.gradle

plugins {
    id "org.sonarqube" version "3.0"
}

sonarqube {
    properties {
        property "sonar.projectName", "${projectName}"
        property "sonar.projectKey", "${projectKey}"
        property "sonar.host.url", "http://${sonarqube_ip}:9000"
        property "sonar.login", "${auth_token}"
        property "sonar.sourceEncoding", "UTF-8"
    }
}

여기까지 하면 다음번에 진행할 Jenkins 빌드 시 sonarqube를 적용할 수 있습니다.
다음 게시글에서는 Jenkins 빌드를 위한 셋팅을 진행해 보겠습니다.

profile
나의 개발 세상

0개의 댓글