springboot-verification
Spring Boot 프로젝트의 검증 루프: 빌드, 정적 분석, 커버리지 포함 테스트, 보안 스캔, 릴리스 또는 PR 직전의 diff 리뷰.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Spring Boot 프로젝트의 검증 루프: 빌드, 정적 분석, 커버리지 포함 테스트, 보안 스캔, 릴리스 또는 PR 직전의 diff 리뷰.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| 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기억할 점: 빠른 피드백이 늦은 사고를 막는다. 게이트는 엄격하게 유지하고, 운영 시스템에서는 경고도 결함으로 취급한다.
새 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 패턴.