springboot-verification
Spring Boot 프로젝트의 검증 루프: 빌드, 정적 분석, 커버리지 포함 테스트, 보안 스캔, 릴리스 또는 PR 직전의 diff 리뷰.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Spring Boot 프로젝트의 검증 루프: 빌드, 정적 분석, 커버리지 포함 테스트, 보안 스캔, 릴리스 또는 PR 직전의 diff 리뷰.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
새 Spring Boot 서비스를 api-gateway + auth-api 에코시스템에 연결한다. auth-api 클라이언트 등록 → Gateway 라우팅 추가 → 서비스에 econo-passport 연동 → 동작 확인까지 한 번에 처리. 다음 상황에서 반드시 이 스킬을 사용한다: - "새 서비스 Gateway에 연결해줘", "새 서비스 auth 연동" - "서비스 등록해줘", "Gateway 뒤에 붙여줘" - "/register-service" 직접 호출 - 새 Spring Boot 서비스가 추가되고 인증이 필요할 때 ARGUMENTS: 서비스명, 서비스 경로(선택), 업스트림 URL(선택) 예: "EEOS-BE /Users/mando/study/eeos/EEOS-BE/eeos"
이 스킬은 사용자가 "PR 생성해줘", "PR 만들어줘", "pull request 생성", "pr 올려줘", "/git-pr" 등을 요청할 때 호출된다. 현재 브랜치의 커밋을 원격으로 push하고, `.github/PULL_REQUEST_TEMPLATE.md`를 채워 GitHub Pull Request를 생성한다.
신규 기능을 개발할 때 정책 문서 → 코드 → 테스트 순서로 작성한다. ADR은 기술적 결정에만 사용하고, 기능 정책은 docs/features/ 에 작성한다. 다음 상황에서 반드시 이 스킬을 사용한다: - "기능 추가해줘", "기획부터 해봐", "설계해봐" - 새로운 API 엔드포인트 또는 도메인 규칙이 생길 때 - "/new-feature" 직접 호출 ARGUMENTS: 기능 이름 또는 요구사항 (없으면 대화에서 추출)
Architecture Decision Record(ADR)를 작성한다. 기술적 결정사항, 설계 선택, 트레이드오프를 문서화하여 나중에 "왜 이렇게 했지?"를 알 수 있게 한다. 다음 상황에서 반드시 이 스킬을 사용한다: - "ADR 써줘", "결정사항 문서화해줘", "이 결정 기록해줘" - 기술 방향 선택 후 ("A 대신 B 쓰기로 했어") - 설계 논의가 끝났을 때 - 나중에 이 결정이 왜 내려졌는지 설명이 필요할 것 같을 때 - "/adr" 직접 호출 ARGUMENTS: 결정 내용 또는 결정 번호 (없으면 대화에서 추출)
AI 에이전트의 액션 스페이스, 도구 정의, 관측(Observation) 포맷을 설계·최적화해 작업 완수율을 높일 때 사용한다.
로컬 개발, 컨테이너 보안, 네트워킹, 볼륨 전략, 멀티 서비스 오케스트레이션을 위한 Docker 및 Docker Compose 패턴.
| name | springboot-verification |
| description | Spring Boot 프로젝트의 검증 루프: 빌드, 정적 분석, 커버리지 포함 테스트, 보안 스캔, 릴리스 또는 PR 직전의 diff 리뷰. |
| origin | ECC |
PR 생성 전, 큰 변경 후, 배포 전에 실행한다.
mvn -T 4 clean verify -DskipTests
# 또는
./gradlew clean assemble -x test
빌드가 실패하면 중단하고 수정한다.
Maven (자주 쓰는 플러그인):
mvn -T 4 spotbugs:check pmd:check checkstyle:check
Gradle (구성된 경우):
./gradlew checkstyleMain pmdMain spotbugsMain
mvn -T 4 test
mvn jacoco:report # 80% 이상 커버리지 확인
# 또는
./gradlew test jacocoTestReport
리포트:
의존성을 mock으로 대체해 서비스 로직을 격리 테스트한다:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepository;
@InjectMocks private UserService userService;
@Nested
@DisplayName("사용자 생성 테스트")
class CreateUserTest {
@Test
@DisplayName("정상 입력 시 사용자 생성 성공")
void createValidUser() {
// given
var dto = new CreateUserDto("Alice", "alice@example.com");
var expected = new User(1L, "Alice", "alice@example.com");
when(userRepository.save(any(User.class))).thenReturn(expected);
// when
var result = userService.create(dto);
// then
assertThat(result.name()).isEqualTo("Alice");
verify(userRepository).save(any(User.class));
}
@Test
@DisplayName("중복 이메일 시 예외 발생")
void createDuplicateEmail() {
// given
var dto = new CreateUserDto("Alice", "existing@example.com");
when(userRepository.existsByEmail(dto.email())).thenReturn(true);
// when & then
assertThatThrownBy(() -> userService.create(dto))
.isInstanceOf(DuplicateEmailException.class);
}
}
}
H2 대신 실제 데이터베이스를 대상으로 테스트한다:
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired private UserRepository userRepository;
@Nested
@DisplayName("이메일 조회 테스트")
class FindByEmailTest {
@Test
@DisplayName("존재하는 사용자 이메일 조회 시 반환")
void findExistingUser() {
// given
userRepository.save(new User("Alice", "alice@example.com"));
// when
var found = userRepository.findByEmail("alice@example.com");
// then
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}
}
전체 Spring 컨텍스트로 컨트롤러 계층을 테스트한다:
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Nested
@DisplayName("사용자 생성 API 테스트")
class CreateUserApiTest {
@Test
@DisplayName("정상 입력 시 201 응답")
void createValidUser() throws Exception {
// given
var user = new UserDto(1L, "Alice", "alice@example.com");
when(userService.create(any())).thenReturn(user);
// when & then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "alice@example.com"}
"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Alice"));
}
@Test
@DisplayName("잘못된 이메일 형식 시 400 응답")
void createInvalidEmail() throws Exception {
// when & then
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "not-an-email"}
"""))
.andExpect(status().isBadRequest());
}
}
}
# 의존성 CVE
mvn org.owasp:dependency-check-maven:check
# 또는
./gradlew dependencyCheckAnalyze
# 소스 내 시크릿
grep -rn "password\s*=\s*\"" src/ --include="*.java" --include="*.yml" --include="*.properties"
grep -rn "sk-\|api_key\|secret" src/ --include="*.java" --include="*.yml"
# 시크릿 (git 히스토리)
git secrets --scan # 구성되어 있다면
# System.out.println 사용 여부 점검 (logger 사용 권장)
grep -rn "System\.out\.print" src/main/ --include="*.java"
# 응답에 원본 예외 메시지가 노출되는지 점검
grep -rn "e\.getMessage()" src/main/ --include="*.java"
# 와일드카드 CORS 점검
grep -rn "allowedOrigins.*\*" src/main/ --include="*.java"
mvn spotless:apply # Spotless 플러그인 사용 시
./gradlew spotlessApply
git diff --stat
git diff
체크리스트:
System.out, 가드 없는 log.debug 등)검증 리포트
===================
빌드: [PASS/FAIL]
정적 분석: [PASS/FAIL] (spotbugs/pmd/checkstyle)
테스트: [PASS/FAIL] (X/Y 통과, Z% 커버리지)
보안: [PASS/FAIL] (CVE 발견: N건)
Diff: [X개 파일 변경]
전체: [READY / NOT READY]
수정할 항목:
1. ...
2. ...
mvn -T 4 test + spotbugs기억할 점: 빠른 피드백이 늦은 사고를 막는다. 게이트는 엄격하게 유지하고, 운영 시스템에서는 경고도 결함으로 취급한다.