springboot-verification
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSpring Boot Verification Loop
Spring Boot 验证流程
Run before PRs, after major changes, and pre-deploy.
在提交PR、重大变更后以及部署前运行。
When to Activate
激活时机
- Before opening a pull request for a Spring Boot service
- After major refactoring or dependency upgrades
- Pre-deployment verification for staging or production
- Running full build → lint → test → security scan pipeline
- Validating test coverage meets thresholds
- 为Spring Boot服务提交拉取请求(PR)前
- 重大重构或依赖升级后
- 预发布环境或生产环境部署前的验证
- 运行完整的构建 → 代码检查 → 测试 → 安全扫描流水线
- 验证测试覆盖率是否达到阈值
Phase 1: Build
阶段1:构建
bash
mvn -T 4 clean verify -DskipTestsbash
mvn -T 4 clean verify -DskipTestsor
or
./gradlew clean assemble -x test
If build fails, stop and fix../gradlew clean assemble -x test
如果构建失败,停止并修复问题。Phase 2: Static Analysis
阶段2:静态分析
Maven (common plugins):
bash
mvn -T 4 spotbugs:check pmd:check checkstyle:checkGradle (if configured):
bash
./gradlew checkstyleMain pmdMain spotbugsMainMaven(常用插件):
bash
mvn -T 4 spotbugs:check pmd:check checkstyle:checkGradle(若已配置):
bash
./gradlew checkstyleMain pmdMain spotbugsMainPhase 3: Tests + Coverage
阶段3:测试 + 覆盖率
bash
mvn -T 4 test
mvn jacoco:report # verify 80%+ coveragebash
mvn -T 4 test
mvn jacoco:report # 验证覆盖率≥80%or
or
./gradlew test jacocoTestReport
Report:
- Total tests, passed/failed
- Coverage % (lines/branches)./gradlew test jacocoTestReport
报告内容:
- 测试总数、通过/失败数量
- 覆盖率百分比(行/分支)Unit Tests
单元测试
Test service logic in isolation with mocked dependencies:
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepository;
@InjectMocks private UserService userService;
@Test
void createUser_validInput_returnsUser() {
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);
var result = userService.create(dto);
assertThat(result.name()).isEqualTo("Alice");
verify(userRepository).save(any(User.class));
}
@Test
void createUser_duplicateEmail_throwsException() {
var dto = new CreateUserDto("Alice", "existing@example.com");
when(userRepository.existsByEmail(dto.email())).thenReturn(true);
assertThatThrownBy(() -> userService.create(dto))
.isInstanceOf(DuplicateEmailException.class);
}
}通过模拟依赖项,独立测试服务逻辑:
java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepository;
@InjectMocks private UserService userService;
@Test
void createUser_validInput_returnsUser() {
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);
var result = userService.create(dto);
assertThat(result.name()).isEqualTo("Alice");
verify(userRepository).save(any(User.class));
}
@Test
void createUser_duplicateEmail_throwsException() {
var dto = new CreateUserDto("Alice", "existing@example.com");
when(userRepository.existsByEmail(dto.email())).thenReturn(true);
assertThatThrownBy(() -> userService.create(dto))
.isInstanceOf(DuplicateEmailException.class);
}
}Integration Tests with Testcontainers
基于Testcontainers的集成测试
Test against a real database instead of H2:
java
@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;
@Test
void findByEmail_existingUser_returnsUser() {
userRepository.save(new User("Alice", "alice@example.com"));
var found = userRepository.findByEmail("alice@example.com");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}针对真实数据库而非H2进行测试:
java
@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;
@Test
void findByEmail_existingUser_returnsUser() {
userRepository.save(new User("Alice", "alice@example.com"));
var found = userRepository.findByEmail("alice@example.com");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}API Tests with MockMvc
基于MockMvc的API测试
Test controller layer with full Spring context:
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
void createUser_validInput_returns201() throws Exception {
var user = new UserDto(1L, "Alice", "alice@example.com");
when(userService.create(any())).thenReturn(user);
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
void createUser_invalidEmail_returns400() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "not-an-email"}
"""))
.andExpect(status().isBadRequest());
}
}在完整Spring上下文环境下测试控制器层:
java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
void createUser_validInput_returns201() throws Exception {
var user = new UserDto(1L, "Alice", "alice@example.com");
when(userService.create(any())).thenReturn(user);
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
void createUser_invalidEmail_returns400() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "not-an-email"}
"""))
.andExpect(status().isBadRequest());
}
}Phase 4: Security Scan
阶段4:安全扫描
bash
undefinedbash
undefinedDependency CVEs
依赖项CVE检测
mvn org.owasp:dependency-check-maven:check
mvn org.owasp:dependency-check-maven:check
or
or
./gradlew dependencyCheckAnalyze
./gradlew dependencyCheckAnalyze
Secrets in source
源代码中的密钥检测
grep -rn "password\s*=\s*"" src/ --include=".java" --include=".yml" --include=".properties"
grep -rn "sk-|api_key|secret" src/ --include=".java" --include="*.yml"
grep -rn "password\s*=\s*"" src/ --include=".java" --include=".yml" --include=".properties"
grep -rn "sk-|api_key|secret" src/ --include=".java" --include="*.yml"
Secrets (git history)
Git历史中的密钥检测
git secrets --scan # if configured
undefinedgit secrets --scan # 若已配置
undefinedCommon Security Findings
常见安全问题检查
undefinedundefinedCheck for System.out.println (use logger instead)
检查是否存在System.out.println(应使用日志框架替代)
grep -rn "System.out.print" src/main/ --include="*.java"
grep -rn "System.out.print" src/main/ --include="*.java"
Check for raw exception messages in responses
检查响应中是否包含原始异常信息
grep -rn "e.getMessage()" src/main/ --include="*.java"
grep -rn "e.getMessage()" src/main/ --include="*.java"
Check for wildcard CORS
检查是否存在通配符CORS配置
grep -rn "allowedOrigins.*" src/main/ --include=".java"
undefinedgrep -rn "allowedOrigins.*" src/main/ --include=".java"
undefinedPhase 5: Lint/Format (optional gate)
阶段5:代码检查/格式化(可选关卡)
bash
mvn spotless:apply # if using Spotless plugin
./gradlew spotlessApplybash
mvn spotless:apply # 若使用Spotless插件
./gradlew spotlessApplyPhase 6: Diff Review
阶段6:差异审查
bash
git diff --stat
git diffChecklist:
- No debugging logs left (,
System.outwithout guards)log.debug - Meaningful errors and HTTP statuses
- Transactions and validation present where needed
- Config changes documented
bash
git diff --stat
git diff检查清单:
- 未遗留调试日志(如无防护的、
System.out)log.debug - 错误信息和HTTP状态码具备明确含义
- 必要位置已添加事务和验证逻辑
- 配置变更已记录
Output Template
输出模板
VERIFICATION REPORT
===================
Build: [PASS/FAIL]
Static: [PASS/FAIL] (spotbugs/pmd/checkstyle)
Tests: [PASS/FAIL] (X/Y passed, Z% coverage)
Security: [PASS/FAIL] (CVE findings: N)
Diff: [X files changed]
Overall: [READY / NOT READY]
Issues to Fix:
1. ...
2. ...验证报告
=========
构建: [通过/失败]
静态分析: [通过/失败](spotbugs/pmd/checkstyle)
测试: [通过/失败](X/Y通过,Z%覆盖率)
安全扫描: [通过/失败](CVE漏洞数量:N)
代码差异: [X个文件变更]
整体状态: [就绪 / 未就绪]
待修复问题:
1. ...
2. ...Continuous Mode
持续运行模式
- Re-run phases on significant changes or every 30–60 minutes in long sessions
- Keep a short loop: + spotbugs for quick feedback
mvn -T 4 test
Remember: Fast feedback beats late surprises. Keep the gate strict—treat warnings as defects in production systems.
- 在重大变更后或长会话中每30–60分钟重新运行流程
- 保留轻量流程:+ spotbugs以快速获取反馈
mvn -T 4 test
谨记:快速反馈优于事后补救。严格把关——在生产环境中,将警告视为缺陷处理。