backend-claude-code 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +42 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- package/.mcp.json +19 -0
- package/CLAUDE.md +126 -0
- package/README.md +142 -0
- package/agents/code-reviewer.md +84 -0
- package/agents/database-reviewer.md +91 -0
- package/agents/java-build-resolver.md +127 -0
- package/agents/java-performance-reviewer.md +262 -0
- package/agents/planner.md +99 -0
- package/agents/security-reviewer.md +119 -0
- package/agents/tdd-guide.md +189 -0
- package/bin/cli.js +144 -0
- package/commands/db-migrate.md +134 -0
- package/commands/dev-build.md +72 -0
- package/commands/dev-coverage.md +73 -0
- package/commands/dev-fix.md +75 -0
- package/commands/dev-plan.md +501 -0
- package/commands/dev-review.md +144 -0
- package/commands/dev-run.md +385 -0
- package/commands/dev-test.md +89 -0
- package/commands/dev-verify.md +95 -0
- package/commands/dev.md +45 -0
- package/commands/git-commit.md +112 -0
- package/commands/git-issue.md +74 -0
- package/commands/git-pr.md +184 -0
- package/commands/git-push.md +28 -0
- package/package.json +24 -0
- package/rules/architecture.md +33 -0
- package/rules/coding-style.md +113 -0
- package/rules/controller-patterns.md +63 -0
- package/rules/dto-patterns.md +76 -0
- package/rules/entity-patterns.md +70 -0
- package/rules/error-handling.md +56 -0
- package/rules/hooks.md +73 -0
- package/rules/repository-patterns.md +75 -0
- package/rules/security.md +101 -0
- package/rules/service-patterns.md +70 -0
- package/rules/testing.md +174 -0
- package/skills/api-design/SKILL.md +523 -0
- package/skills/architecture-decision-records/SKILL.md +179 -0
- package/skills/database-migrations/SKILL.md +429 -0
- package/skills/hexagonal-architecture/SKILL.md +276 -0
- package/skills/java-coding-standards/SKILL.md +236 -0
- package/skills/jpa-patterns/SKILL.md +218 -0
- package/skills/postgres-patterns/SKILL.md +147 -0
- package/skills/springboot-patterns/SKILL.md +255 -0
- package/skills/springboot-security/SKILL.md +241 -0
- package/skills/springboot-tdd/SKILL.md +236 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: springboot-tdd
|
|
3
|
+
description: Test-driven development for Spring Boot using JUnit 5, Mockito, MockMvc, Testcontainers, and JaCoCo. Use when adding features, fixing bugs, or refactoring.
|
|
4
|
+
origin: ECC
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Spring Boot TDD Workflow
|
|
8
|
+
|
|
9
|
+
JUnit 5, Mockito, MockMvc, Testcontainers를 사용한 Spring Boot TDD — 80%+ 커버리지.
|
|
10
|
+
|
|
11
|
+
## When to Use
|
|
12
|
+
|
|
13
|
+
- 새 기능 또는 엔드포인트
|
|
14
|
+
- 버그 수정 또는 리팩토링
|
|
15
|
+
- 데이터 접근 로직 또는 보안 규칙 추가
|
|
16
|
+
|
|
17
|
+
## Workflow
|
|
18
|
+
|
|
19
|
+
1) 먼저 테스트 작성 (실패해야 함)
|
|
20
|
+
2) 통과하는 최소 코드 구현
|
|
21
|
+
3) 테스트 통과 상태에서 리팩토링
|
|
22
|
+
4) JaCoCo로 커버리지 강제 적용
|
|
23
|
+
|
|
24
|
+
## Unit Tests (JUnit 5 + Mockito)
|
|
25
|
+
|
|
26
|
+
```java
|
|
27
|
+
@ExtendWith(MockitoExtension.class)
|
|
28
|
+
class OrderServiceTest {
|
|
29
|
+
@Mock OrderRepository repo;
|
|
30
|
+
@InjectMocks OrderService service;
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
@DisplayName("create — 유효한 요청으로 주문 생성")
|
|
34
|
+
void create_validRequest_createsOrder() {
|
|
35
|
+
var req = new CreateOrderRequest("Alice", BigDecimal.TEN);
|
|
36
|
+
when(repo.save(any())).thenAnswer(inv -> {
|
|
37
|
+
OrderEntity e = inv.getArgument(0);
|
|
38
|
+
return new OrderEntity(1L, e.getCustomerName(), e.getAmount());
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
var result = service.create(req);
|
|
42
|
+
|
|
43
|
+
assertThat(result.customerName()).isEqualTo("Alice");
|
|
44
|
+
assertThat(result.id()).isEqualTo(1L);
|
|
45
|
+
verify(repo).save(any());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
@DisplayName("findById — 존재하지 않는 ID로 예외 발생")
|
|
50
|
+
void findById_notFound_throwsException() {
|
|
51
|
+
when(repo.findById(99L)).thenReturn(Optional.empty());
|
|
52
|
+
|
|
53
|
+
assertThatThrownBy(() -> service.findById(99L))
|
|
54
|
+
.isInstanceOf(OrderNotFoundException.class)
|
|
55
|
+
.hasMessageContaining("99");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Web Layer Tests (MockMvc)
|
|
61
|
+
|
|
62
|
+
```java
|
|
63
|
+
@WebMvcTest(OrderController.class)
|
|
64
|
+
class OrderControllerTest {
|
|
65
|
+
@Autowired MockMvc mockMvc;
|
|
66
|
+
@MockBean OrderService orderService;
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
@DisplayName("GET /orders/{id} — 주문 반환")
|
|
70
|
+
void getOrder_returns200() throws Exception {
|
|
71
|
+
when(orderService.findById(1L)).thenReturn(sampleResponse());
|
|
72
|
+
|
|
73
|
+
mockMvc.perform(get("/api/orders/1"))
|
|
74
|
+
.andExpect(status().isOk())
|
|
75
|
+
.andExpect(jsonPath("$.data.customerName").value("Alice"));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Test
|
|
79
|
+
@DisplayName("POST /orders — 유효하지 않은 입력 시 400 반환")
|
|
80
|
+
void createOrder_invalidInput_returns400() throws Exception {
|
|
81
|
+
mockMvc.perform(post("/api/orders")
|
|
82
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
83
|
+
.content("""{"customerName":"","amount":-1}"""))
|
|
84
|
+
.andExpect(status().isBadRequest());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Test
|
|
88
|
+
@DisplayName("GET /orders/{id} — 존재하지 않는 ID 시 404 반환")
|
|
89
|
+
void getOrder_notFound_returns404() throws Exception {
|
|
90
|
+
when(orderService.findById(99L))
|
|
91
|
+
.thenThrow(new OrderNotFoundException(99L));
|
|
92
|
+
|
|
93
|
+
mockMvc.perform(get("/api/orders/99"))
|
|
94
|
+
.andExpect(status().isNotFound());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Integration Tests (SpringBootTest)
|
|
100
|
+
|
|
101
|
+
```java
|
|
102
|
+
@SpringBootTest
|
|
103
|
+
@AutoConfigureMockMvc
|
|
104
|
+
@ActiveProfiles("test")
|
|
105
|
+
class OrderIntegrationTest {
|
|
106
|
+
@Autowired MockMvc mockMvc;
|
|
107
|
+
|
|
108
|
+
@Test
|
|
109
|
+
@DisplayName("주문 생성 → 조회 통합 테스트")
|
|
110
|
+
void createAndRetrieveOrder() throws Exception {
|
|
111
|
+
// 생성
|
|
112
|
+
var result = mockMvc.perform(post("/api/orders")
|
|
113
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
114
|
+
.content("""{"customerName":"Bob","amount":50}"""))
|
|
115
|
+
.andExpect(status().isCreated())
|
|
116
|
+
.andReturn();
|
|
117
|
+
|
|
118
|
+
// ID 추출 후 조회
|
|
119
|
+
var id = JsonPath.read(result.getResponse().getContentAsString(), "$.data.id");
|
|
120
|
+
mockMvc.perform(get("/api/orders/" + id))
|
|
121
|
+
.andExpect(status().isOk())
|
|
122
|
+
.andExpect(jsonPath("$.data.customerName").value("Bob"));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Persistence Tests (DataJpaTest)
|
|
128
|
+
|
|
129
|
+
```java
|
|
130
|
+
@DataJpaTest
|
|
131
|
+
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
|
132
|
+
@Import(TestContainersConfig.class)
|
|
133
|
+
class OrderRepositoryTest {
|
|
134
|
+
@Autowired OrderRepository repo;
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
@DisplayName("저장 및 조회")
|
|
138
|
+
void saveAndFind() {
|
|
139
|
+
var entity = new OrderEntity(null, "Alice", BigDecimal.TEN);
|
|
140
|
+
var saved = repo.save(entity);
|
|
141
|
+
|
|
142
|
+
var found = repo.findById(saved.getId());
|
|
143
|
+
assertThat(found).isPresent();
|
|
144
|
+
assertThat(found.get().getCustomerName()).isEqualTo("Alice");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Testcontainers Config
|
|
150
|
+
|
|
151
|
+
```java
|
|
152
|
+
// src/test/java/com/example/config/TestContainersConfig.java
|
|
153
|
+
@Configuration
|
|
154
|
+
public class TestContainersConfig {
|
|
155
|
+
|
|
156
|
+
@Container
|
|
157
|
+
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
|
|
158
|
+
.withReuse(true);
|
|
159
|
+
|
|
160
|
+
static {
|
|
161
|
+
postgres.start();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@DynamicPropertySource
|
|
165
|
+
static void registerProperties(DynamicPropertyRegistry registry) {
|
|
166
|
+
registry.add("spring.datasource.url", postgres::getJdbcUrl);
|
|
167
|
+
registry.add("spring.datasource.username", postgres::getUsername);
|
|
168
|
+
registry.add("spring.datasource.password", postgres::getPassword);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Coverage (JaCoCo)
|
|
174
|
+
|
|
175
|
+
Maven:
|
|
176
|
+
```xml
|
|
177
|
+
<plugin>
|
|
178
|
+
<groupId>org.jacoco</groupId>
|
|
179
|
+
<artifactId>jacoco-maven-plugin</artifactId>
|
|
180
|
+
<version>0.8.14</version>
|
|
181
|
+
<executions>
|
|
182
|
+
<execution><goals><goal>prepare-agent</goal></goals></execution>
|
|
183
|
+
<execution>
|
|
184
|
+
<id>report</id>
|
|
185
|
+
<phase>verify</phase>
|
|
186
|
+
<goals><goal>report</goal></goals>
|
|
187
|
+
</execution>
|
|
188
|
+
<execution>
|
|
189
|
+
<id>check</id>
|
|
190
|
+
<goals><goal>check</goal></goals>
|
|
191
|
+
<configuration>
|
|
192
|
+
<rules>
|
|
193
|
+
<rule>
|
|
194
|
+
<limits>
|
|
195
|
+
<limit>
|
|
196
|
+
<counter>LINE</counter>
|
|
197
|
+
<value>COVEREDRATIO</value>
|
|
198
|
+
<minimum>0.80</minimum>
|
|
199
|
+
</limit>
|
|
200
|
+
</limits>
|
|
201
|
+
</rule>
|
|
202
|
+
</rules>
|
|
203
|
+
</configuration>
|
|
204
|
+
</execution>
|
|
205
|
+
</executions>
|
|
206
|
+
</plugin>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Assertions Cheatsheet
|
|
210
|
+
|
|
211
|
+
```java
|
|
212
|
+
// 기본
|
|
213
|
+
assertThat(result).isEqualTo(expected);
|
|
214
|
+
assertThat(list).hasSize(3).containsExactly(a, b, c);
|
|
215
|
+
assertThat(optional).isPresent().hasValue(expected);
|
|
216
|
+
|
|
217
|
+
// 예외
|
|
218
|
+
assertThatThrownBy(() -> service.method())
|
|
219
|
+
.isInstanceOf(MyException.class)
|
|
220
|
+
.hasMessageContaining("expected text");
|
|
221
|
+
|
|
222
|
+
// BigDecimal (정밀도 무관)
|
|
223
|
+
assertThat(price).isEqualByComparingTo(new BigDecimal("10.00"));
|
|
224
|
+
|
|
225
|
+
// JSON path
|
|
226
|
+
mockMvc.perform(get("/api/orders/1"))
|
|
227
|
+
.andExpect(jsonPath("$.data.id").value(1))
|
|
228
|
+
.andExpect(jsonPath("$.data.customerName").value("Alice"));
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## CI Commands
|
|
232
|
+
|
|
233
|
+
- Maven: `./mvnw -T 4 test` 또는 `./mvnw verify`
|
|
234
|
+
- Gradle: `./gradlew test jacocoTestReport`
|
|
235
|
+
|
|
236
|
+
**Remember**: 테스트를 빠르게, 격리되게, 결정적으로 유지. 구현 세부사항이 아닌 동작을 테스트.
|