@@ -8,7 +8,8 @@ plugins {
88 kotlin(Plugins .Kotlin .Short .SPRING ) version Versions .KOTLIN
99 kotlin(Plugins .Kotlin .Short .JPA ) version Versions .KOTLIN
1010 id(Plugins .DETEKT ) version Versions .DETEKT
11- id(Plugins .KOVER ) version Versions .KOVER
11+ id(Plugins .JACOCO )
12+ id(Plugins .SONAR_QUBE ) version Versions .SONAR_QUBE
1213}
1314
1415allprojects {
@@ -19,29 +20,41 @@ allprojects {
1920 }
2021}
2122
23+ // 테스트하지 않는 코드 패턴 (JaCoCo + SonarQube 커버리지 + CPD 공통)
24+ val testExclusionPatterns = listOf (
25+ " **/*Application*" ,
26+ " **/config/**" ,
27+ " **/*Config*" ,
28+ " **/exception/**" ,
29+ " **/*Exception*" ,
30+ " **/*ErrorCode*" ,
31+ " **/dto/**" ,
32+ " **/*Request*" ,
33+ " **/*Response*" ,
34+ " **/*Entity*" ,
35+ " **/annotation/**" ,
36+ " **/generated/**"
37+ )
38+
39+ // SonarQube 전체 분석 제외 패턴 (분석 자체가 의미 없는 파일들)
40+ val sonarGlobalExclusions = listOf (
41+ " **/build/**" ,
42+ )
43+
2244subprojects {
2345 apply (plugin = Plugins .SPRING_BOOT )
2446 apply (plugin = Plugins .SPRING_DEPENDENCY_MANAGEMENT )
2547 apply (plugin = Plugins .Kotlin .SPRING )
2648 apply (plugin = Plugins .Kotlin .JPA )
2749 apply (plugin = Plugins .Kotlin .JVM )
50+ apply (plugin = Plugins .JACOCO )
2851
2952 java {
3053 toolchain {
3154 languageVersion.set(JavaLanguageVersion .of(Versions .JAVA_VERSION .toInt()))
3255 }
3356 }
3457
35- dependencyManagement {
36- imports {
37- mavenBom(" org.springframework.cloud:spring-cloud-dependencies:2025.0.0" )
38- }
39- }
40-
41- tasks.withType<Test > {
42- useJUnitPlatform()
43- }
44-
4558 plugins.withId(Plugins .Kotlin .ALLOPEN ) {
4659 extensions.configure< org.jetbrains.kotlin.allopen.gradle.AllOpenExtension > {
4760 annotation(" jakarta.persistence.Entity" )
@@ -53,14 +66,150 @@ subprojects {
5366 // Configure Kotlin compiler options
5467 tasks.withType< org.jetbrains.kotlin.gradle.tasks.KotlinCompile > {
5568 kotlinOptions {
56- freeCompilerArgs = listOf (" -Xjsr305=strict" )
69+ freeCompilerArgs + = listOf (
70+ " -Xjsr305=strict" ,
71+ " -Xconsistent-data-class-copy-visibility"
72+ )
5773 jvmTarget = Versions .JAVA_VERSION
58- freeCompilerArgs + = " -Xconsistent-data-class-copy-visibility"
5974 }
6075 }
6176}
6277
78+ // 루트 프로젝트에서 모든 JaCoCo 설정 관리
79+ configure(subprojects) {
80+ jacoco {
81+ toolVersion = Versions .JACOCO
82+ }
83+
84+ tasks.withType<Test > {
85+ useJUnitPlatform()
86+ finalizedBy(" jacocoTestReport" )
87+
88+ testLogging {
89+ events(" passed" , " skipped" , " failed" )
90+ showStandardStreams = false
91+ }
92+ }
93+
94+ // 각 서브모듈의 JaCoCo 테스트 리포트 설정
95+ tasks.withType<JacocoReport > {
96+ dependsOn(" test" )
97+ reports {
98+ xml.required.set(true )
99+ csv.required.set(false )
100+ html.required.set(true )
101+ }
102+
103+ classDirectories.setFrom(fileTree(layout.buildDirectory.dir(" classes/kotlin/main" )) {
104+ exclude(testExclusionPatterns)
105+ })
106+
107+ executionData.setFrom(fileTree(layout.buildDirectory) {
108+ include(" jacoco/*.exec" )
109+ })
110+ }
111+ }
112+
63113tasks {
64114 withType<Jar > { enabled = true }
65115 withType<BootJar > { enabled = false }
66116}
117+
118+ // 루트 프로젝트 JaCoCo 통합 리포트 설정
119+ tasks.register<JacocoReport >(" jacocoRootReport" ) {
120+ description = " Generates an aggregate report from all subprojects"
121+ group = " reporting"
122+
123+ dependsOn(subprojects.map { it.tasks.named(" test" ) })
124+
125+ sourceDirectories.setFrom(subprojects.map { it.the<SourceSetContainer >()[" main" ].allSource.srcDirs })
126+ classDirectories.setFrom(subprojects.map { subproject ->
127+ subproject.fileTree(subproject.layout.buildDirectory.get().asFile.resolve(" classes/kotlin/main" )) {
128+ exclude(testExclusionPatterns)
129+ }
130+ })
131+ executionData.from(subprojects.map { subproject ->
132+ subproject.fileTree(subproject.layout.buildDirectory.dir(" jacoco" )) {
133+ include(" **/*.exec" )
134+ }
135+ })
136+
137+ reports {
138+ xml.required.set(true )
139+ csv.required.set(false )
140+ html.required.set(true )
141+ }
142+ }
143+
144+ // SonarQube 설정을 루트에서 모든 서브모듈에 대해 설정
145+ sonar {
146+ properties {
147+ property(" sonar.projectKey" , " YAPP-Github_26th-App-Team-1-BE" )
148+ property(" sonar.organization" , " yapp-github" )
149+ property(" sonar.host.url" , " https://sonarcloud.io" )
150+ property(
151+ " sonar.coverage.jacoco.xmlReportPaths" ,
152+ " ${layout.buildDirectory.get()} /reports/jacoco/jacocoRootReport/jacocoRootReport.xml"
153+ )
154+ property(" sonar.kotlin.coveragePlugin" , Plugins .JACOCO )
155+ property(" sonar.kotlin.version" , Versions .KOTLIN )
156+ property(" sonar.exclusions" , sonarGlobalExclusions.joinToString(" ," ))
157+ property(" sonar.cpd.exclusions" , testExclusionPatterns.joinToString(" ," ))
158+ property(" sonar.coverage.exclusions" , testExclusionPatterns.joinToString(" ," ))
159+ }
160+ }
161+
162+ // SonarQube 태스크가 통합 JaCoCo 리포트에 의존하도록 설정
163+ tasks.named(" sonar" ) {
164+ dependsOn(" jacocoRootReport" )
165+ }
166+
167+ /* *
168+ * CI용 - 전체 품질 검증 파이프라인을 실행합니다. (테스트, 커버리지, SonarQube 분석)
169+ * GitHub Actions에서 이 태스크 하나만 호출합니다.
170+ * 사용 예: ./gradlew fullCheck
171+ */
172+ tasks.register(" fullCheck" ) {
173+ description = " Runs all tests, generates reports, and performs SonarQube analysis"
174+ group = " Verification"
175+ dependsOn(" testAll" , " jacocoTestReportAll" )
176+ finalizedBy(" sonar" )
177+ }
178+
179+ /* *
180+ * 로컬용 - SonarQube 분석 없이 빠르게 테스트 커버리지만 확인합니다.
181+ * 사용 예: ./gradlew checkCoverage
182+ */
183+ tasks.register(" checkCoverage" ) {
184+ description = " Runs tests and generates coverage reports without SonarQube analysis"
185+ group = " Verification"
186+ dependsOn(" testAll" , " jacocoTestReportAll" )
187+ }
188+
189+ /* *
190+ * 로컬용 - 빌드 과정에서 생성된 모든 리포트를 삭제합니다.
191+ * 사용 예: ./gradlew cleanReports
192+ */
193+ tasks.register(" cleanReports" ) {
194+ description = " Cleans all generated reports"
195+ group = " Cleanup"
196+ doLast {
197+ subprojects.forEach { subproject ->
198+ delete(subproject.layout.buildDirectory.dir(" reports" ))
199+ }
200+ delete(layout.buildDirectory.dir(" reports" ))
201+ }
202+ }
203+
204+ tasks.register(" testAll" ) {
205+ description = " Runs tests in all subprojects"
206+ group = " Verification"
207+ dependsOn(subprojects.map { it.tasks.named(" test" ) })
208+ }
209+
210+ tasks.register(" jacocoTestReportAll" ) {
211+ description = " Generates JaCoCo test reports for all subprojects and creates aggregate report"
212+ group = " Verification"
213+ dependsOn(subprojects.map { it.tasks.named(" jacocoTestReport" ) })
214+ finalizedBy(" jacocoRootReport" )
215+ }
0 commit comments