Skip to content

Commit bbd97af

Browse files
authored
Jacoco 도입 (#244)
* chore: jacoco 설정 * chore: CI workflow에 jacoco report 업로드 작업 추가 * chore: jacoco report 생성 경로를 기본값으로 변경 * chore: jacoco report 업로드 확인을 위해 임시로 커버리지 기준을 60으로 설정 * chore: jacoco 커버리지 기준 70으로 설정 및 update-comment 활성화 * chore: 이전 step들의 실패 여부와 상관없이 무조건 jacoco report 실행 * chore: jacoco에서 Q클래스 제외 * test: SuccessResponseCustomizer customize 단위 테스트 * chore: 변경된 파일에 대한 커버리지 누락 확인을 위해 'update-comment:true' 임시 주석 처리 * chore: 'update-comment:true' 주석 해제 및 Q클래스 제외 규칙 변경 * chore: CI 워크플로우에 Get the Coverage info 추가 * test: jacoco 변경 파일에 대한 커버리지 체크 여부 확인을 위한 테스트 임시 주석 * test: jacoco-report v1.6.1로 다운그레이드 * test: jacoco-report v1.7.2로 업그레이드 * test: jacoco report 확인을 위한 임의 클래스 및 테스트 코드 작성 * Revert "test: jacoco report 확인을 위한 임의 클래스 및 테스트 코드 작성" This reverts commit a1f7a59.
1 parent b130ddb commit bbd97af

3 files changed

Lines changed: 135 additions & 4 deletions

File tree

.github/workflows/continuous-intergration.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,18 @@ jobs:
4848
files: build/test-results/**/*.xml
4949

5050
- name: Upload Jacoco Report
51-
if: ${{ failure() }}
52-
uses: actions/upload-artifact@v4.6.0
51+
id: jacoco
52+
uses: madrapps/jacoco-report@v1.7.2
53+
if: always()
5354
with:
54-
name: jacoco-report
55-
path: build/reports/jacoco/test/html
55+
title: 📊테스트 커버리지
56+
paths: ${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml
57+
token: ${{ secrets.GITHUB_TOKEN }}
58+
min-coverage-overall: 70
59+
min-coverage-changed-files: 70
60+
update-comment: true
61+
62+
- name: Get the Coverage info
63+
run: |
64+
echo "Total coverage ${{ steps.jacoco.outputs.coverage-overall }}"
65+
echo "Changed Files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}"

build.gradle

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'java'
33
id 'org.springframework.boot' version '3.4.1'
44
id 'io.spring.dependency-management' version '1.1.7'
5+
id 'jacoco'
56
}
67

78
group = 'org.ject'
@@ -24,6 +25,10 @@ repositories {
2425
maven { url 'https://s3-us-west-2.amazonaws.com/dynamodb-local/release' }
2526
}
2627

28+
jacoco {
29+
toolVersion = "0.8.12"
30+
}
31+
2732
dependencies {
2833
// Boot
2934
implementation 'org.springframework.boot:spring-boot-starter-web'
@@ -131,13 +136,55 @@ configurations {
131136
querydsl.extendsFrom compileClasspath
132137
}
133138

139+
def jacocoExcludes = [
140+
'**/Q*', // QueryDSL Q*
141+
'**/exception/**',
142+
'**/dto/**'
143+
]
144+
134145
test {
135146
useJUnitPlatform(){
136147
// throughput test 제외
137148
excludeTags 'throughput'
138149
}
139150
// test profile로 테스트
140151
systemProperty 'spring.profiles.active', 'test'
152+
153+
// 테스트 종료 후 jacoco 실행
154+
finalizedBy(jacocoTestReport)
155+
}
156+
157+
jacocoTestReport {
158+
reports {
159+
html.required.set(true)
160+
xml.required.set(true)
161+
}
162+
163+
afterEvaluate {
164+
classDirectories.setFrom(
165+
files(classDirectories.files.collect {
166+
fileTree(dir: it, excludes: jacocoExcludes)
167+
})
168+
)
169+
}
170+
171+
finalizedBy(jacocoTestCoverageVerification)
172+
}
173+
174+
jacocoTestCoverageVerification {
175+
violationRules {
176+
rule {
177+
enabled = true // 규칙 활성화
178+
179+
limit {
180+
counter = "LINE" // 라인 커버리지
181+
value = "COVEREDRATIO"
182+
minimum = 0.70 // 최소 70% 이상 만족
183+
}
184+
185+
excludes = jacocoExcludes
186+
}
187+
}
141188
}
142189

143190
def generated = 'src/main/generated'
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.ject.support.common.springdoc;
2+
3+
import io.swagger.v3.oas.models.Operation;
4+
import io.swagger.v3.oas.models.media.Content;
5+
import io.swagger.v3.oas.models.media.MediaType;
6+
import io.swagger.v3.oas.models.media.ObjectSchema;
7+
import io.swagger.v3.oas.models.media.Schema;
8+
import io.swagger.v3.oas.models.media.StringSchema;
9+
import io.swagger.v3.oas.models.responses.ApiResponse;
10+
import io.swagger.v3.oas.models.responses.ApiResponses;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.util.Map;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.ject.support.common.response.ApiResponseConstant.SUCCESS_STATUS_MESSAGE;
17+
18+
class SuccessResponseCustomizerTest {
19+
20+
SuccessResponseCustomizer customizer = new SuccessResponseCustomizer();
21+
22+
@Test
23+
void 응답이_2xx이면_공통스키마로_래핑한다() {
24+
Operation op = new Operation();
25+
ApiResponses responses = new ApiResponses();
26+
op.setResponses(responses);
27+
28+
ApiResponse ok = new ApiResponse();
29+
Content okContent = new Content();
30+
MediaType okJson = new MediaType();
31+
Schema<?> original200 = new StringSchema();
32+
okJson.setSchema(original200);
33+
okContent.addMediaType("application/json", okJson);
34+
ok.setContent(okContent);
35+
responses.addApiResponse("200", ok);
36+
37+
// when
38+
customizer.customize(op, null);
39+
40+
// then
41+
Schema<?> wrapped200 = op.getResponses()
42+
.get("200").getContent().get("application/json").getSchema();
43+
Map<String, Schema> properties = wrapped200.getProperties();
44+
assertThat(properties).isNotNull();
45+
assertThat(original200).isEqualTo(properties.get("data"));
46+
assertThat(SUCCESS_STATUS_MESSAGE).isEqualTo(properties.get("status").getExample());
47+
assertThat("2025-08-08T14:10:32.123").isEqualTo(properties.get("timestamp").getExample());
48+
}
49+
50+
@Test
51+
void 응답이_2xx가_아니면_래핑하지_않는다() {
52+
// given
53+
Operation op = new Operation();
54+
ApiResponses responses = new ApiResponses();
55+
op.setResponses(responses);
56+
57+
ApiResponse bad = new ApiResponse();
58+
Content badContent = new Content();
59+
MediaType badJson = new MediaType();
60+
Schema<?> original400 = new ObjectSchema();
61+
badJson.setSchema(original400);
62+
badContent.addMediaType("application/json", badJson);
63+
bad.setContent(badContent);
64+
responses.addApiResponse("400", bad);
65+
66+
// when
67+
customizer.customize(op, null);
68+
69+
// then
70+
Schema<?> after = op.getResponses().get("400")
71+
.getContent().get("application/json").getSchema();
72+
assertThat(original400).isEqualTo(after);
73+
}
74+
}

0 commit comments

Comments
 (0)