Skip to content

Commit 94071de

Browse files
committed
new post test coverage
1 parent e4f5d56 commit 94071de

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed

2025-04-09-test-coverage-ignore.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
layout : post
3+
title : 효과적인 테스트 커버리지 관리: 비즈니스 로직에 집중하기
4+
summary : 의미 있는 테스트 커버리지를 달성하기 위한 전략과 실천 방안을 알아봅니다.
5+
date : 2025-04-09 10:30:00 +0900
6+
tags : [Testing, JUnit, Jacoco, TDD, Java, SpringBoot, CleanCode]
7+
toc : true
8+
comment : true
9+
public : true
10+
adsense : true
11+
---
12+
13+
* TOC
14+
{:toc}
15+
16+
# 효과적인 테스트 커버리지 관리: 비즈니스 로직에 집중하기
17+
18+
## 1. 문제 인식
19+
20+
많은 개발 팀들이 테스트 커버리지 80% 이상을 목표로 삼고 있습니다. 하지만 이런 수치적 목표는 몇 가지 문제를 야기할 수 있습니다:
21+
22+
### 1.1. 의미 없는 테스트 코드 증가
23+
24+
```java
25+
@Test
26+
void dto_getter_테스트() { // 이런 테스트가 필요할까요?
27+
ProductDto dto = new ProductDto("상품", 1000);
28+
assertThat(dto.getName()).isEqualTo("상품");
29+
assertThat(dto.getPrice()).isEqualTo(1000);
30+
}
31+
32+
@Test
33+
void config_빈_등록_테스트() { // 스프링이 이미 보장하는 것을 우리가 테스트해야 할까요?
34+
@Configuration
35+
class TestConfig {
36+
@Bean
37+
public RestTemplate restTemplate() {
38+
return new RestTemplate();
39+
}
40+
}
41+
42+
var context = new AnnotationConfigApplicationContext(TestConfig.class);
43+
assertThat(context.getBean(RestTemplate.class)).isNotNull();
44+
}
45+
```
46+
47+
### 1.2. 테스트 유지보수 비용 증가
48+
49+
- 의미 없는 테스트들로 인해 빌드 시간 증가
50+
- 설정 변경 시 불필요한 테스트 수정 필요
51+
- 테스트 코드 리뷰에 들이는 시간 낭비
52+
53+
### 1.3. 잘못된 품질 지표
54+
55+
```java
56+
@Getter
57+
@Setter
58+
public class SimpleDto {
59+
private String field1;
60+
private String field2;
61+
// ... 50개의 필드
62+
}
63+
64+
// 이런 테스트는 커버리지는 높여주지만 품질 향상에는 기여하지 않습니다
65+
@Test
66+
void 모든_필드_테스트() {
67+
SimpleDto dto = new SimpleDto();
68+
dto.setField1("value1");
69+
assertThat(dto.getField1()).isEqualTo("value1");
70+
// ... 50개 필드 모두 테스트
71+
}
72+
```
73+
74+
## 2. 해결 방안: 의미 있는 테스트 커버리지 전략
75+
76+
### 2.1. 테스트 제외 대상 명확히 하기
77+
78+
```gradle
79+
jacocoTestReport {
80+
afterEvaluate {
81+
classDirectories.setFrom(files(classDirectories.files.collect {
82+
fileTree(dir: it, exclude: [
83+
"**/Q*.class", // Querydsl
84+
"**/*Test*.*", // 테스트 클래스
85+
"**/generated/**", // OpenAPI 생성 코드
86+
"**/dto/**", // DTO 클래스
87+
"**/config/**", // 설정 클래스
88+
"**/Application.class" // 메인 애플리케이션 클래스
89+
])
90+
}))
91+
}
92+
}
93+
```
94+
95+
### 2.2. 테스트가 필요한 영역 식별하기
96+
97+
#### 2.2.1. 반드시 테스트해야 할 것
98+
99+
1. **도메인 로직이 있는 Entity**
100+
```java
101+
@Entity
102+
public class Order {
103+
public void apply(Coupon coupon) {
104+
if (!coupon.isValidFor(this)) {
105+
throw new InvalidCouponException();
106+
}
107+
this.discount = coupon.calculateDiscount(this.amount);
108+
this.couponUsed = true;
109+
}
110+
}
111+
112+
@Test
113+
void 주문에_쿠폰_적용시_할인금액이_정확히_계산되어야_한다() {
114+
// given
115+
var order = OrderTestFixture.createWithAmount(10000);
116+
var coupon = CouponTestFixture.createPercentage(10); // 10% 할인
117+
118+
// when
119+
order.apply(coupon);
120+
121+
// then
122+
assertThat(order.getDiscount()).isEqualTo(1000);
123+
assertThat(order.isCouponUsed()).isTrue();
124+
}
125+
```
126+
127+
2. **비즈니스 로직이 있는 Service**
128+
```java
129+
@Test
130+
void 재고가_부족한_상품_주문시_실패해야_한다() {
131+
// given
132+
var product = ProductTestFixture.createWithStock(0);
133+
var orderRequest = OrderRequestTestFixture.create(product.getId(), 1);
134+
135+
// when & then
136+
assertThatThrownBy(() -> orderService.createOrder(orderRequest))
137+
.isInstanceOf(OutOfStockException.class);
138+
}
139+
```
140+
141+
#### 2.2.2. 테스트가 불필요한 것
142+
143+
1. **단순 DTO**
144+
```java
145+
@Getter
146+
@AllArgsConstructor
147+
public class ProductResponse {
148+
private String name;
149+
private BigDecimal price;
150+
// 단순 데이터 전달 객체는 테스트가 불필요
151+
}
152+
```
153+
154+
2. **설정 클래스**
155+
```java
156+
@Configuration
157+
public class WebConfig implements WebMvcConfigurer {
158+
@Override
159+
public void addCorsMappings(CorsRegistry registry) {
160+
registry.addMapping("/**")
161+
.allowedOrigins("*");
162+
}
163+
// 프레임워크가 보장하는 동작은 테스트 불필요
164+
}
165+
```
166+
167+
## 3. 실천 방안
168+
169+
### 3.1. 테스트 우선순위 정하기
170+
171+
1. 높은 우선순위
172+
- 핵심 비즈니스 로직
173+
- 도메인 규칙
174+
- 중요한 유효성 검사
175+
176+
2. 중간 우선순위
177+
- 인프라 코드
178+
- 외부 시스템 연동
179+
180+
3. 낮은 우선순위
181+
- 설정 클래스
182+
- 단순 DTO
183+
- 자동 생성 코드
184+
185+
### 3.2. 테스트 가치 평가 체크리스트
186+
187+
- [ ] 이 테스트는 비즈니스 요구사항을 검증하는가?
188+
- [ ] 이 테스트가 실패하면 실제 문제가 있다는 것을 의미하는가?
189+
- [ ] 이 테스트는 코드의 동작을 명확하게 설명하는가?
190+
- [ ] 이 테스트는 유지보수 비용 대비 충분한 가치가 있는가?
191+
192+
## 4. 결론
193+
194+
테스트 커버리지는 코드 품질을 측정하는 하나의 지표일 뿐입니다. 진정한 목표는 신뢰할 수 있는 소프트웨어를 만드는 것입니다. 따라서:
195+
196+
1. 의미 있는 비즈니스 로직에 집중하여 테스트를 작성하세요
197+
2. 자동 생성된 코드나 단순 구조체는 과감히 제외하세요
198+
3. 테스트의 가치를 항상 고민하고 평가하세요
199+
200+
이를 통해 테스트는 단순한 커버리지 수치가 아닌, 실제 코드 품질 향상에 기여할 수 있습니다.
201+
202+
> "테스트의 목적은 버그를 찾는 것이 아니라, 코드가 의도대로 동작한다는 확신을 주는 것입니다."
203+
204+
## 참고 자료
205+
206+
- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)
207+
- [Jacoco Documentation](https://www.jacoco.org/jacoco/trunk/doc/)
208+
- [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882)
209+
- [Test Driven Development: By Example](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530)
210+

0 commit comments

Comments
 (0)