Quarkus 驗證循環 是做什麼的?
在 PR、重大變更後以及部署前執行。
啟用時機
- 在為 Quarkus 服務開立 Pull Request 之前
- 重大重構或相依套件升級後
- 預備部署至 Staging 或 Production 環境前
- 執行完整建置 → 程式碼檢查 → 測試 → 安全掃描 → 原生編譯流程
- 驗證測試覆蓋率是否符合門檻 (80%+)
- 測試原生映像檔相容性
第一階段:建置
# Maven
mvn clean verify -DskipTests
# Gradle
./gradlew clean assemble -x test
如果建置失敗,請停止並修正編譯錯誤。
第二階段:靜態分析
Checkstyle、PMD、SpotBugs (Maven)
mvn checkstyle:check pmd:check spotbugs:check
SonarQube(若有設定)
mvn sonar:sonar \
-Dsonar.projectKey=my-quarkus-project \
-Dsonar.host.url=http://localhost:9000 \
-Dsonar.login=${SONAR_TOKEN}
需處理的常見問題
- 未使用的匯入或變數
- 複雜的方法(過高的循環複雜度)
- 可能的空指標取值
- SpotBugs 標記的安全問題
第三階段:測試 + 覆蓋率
# 執行所有測試
mvn clean test
# 產生覆蓋率報告
mvn jacoco:report
# 強制覆蓋率門檻 (80%)
mvn jacoco:check
# 或使用 Gradle
./gradlew test jacocoTestReport jacocoTestCoverageVerification
測試類別
單元測試
使用模擬的相依物件測試服務邏輯:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock UserRepository userRepository;
@InjectMocks UserService userService;
@Test
void createUser_validInput_returnsUser() {
var dto = new CreateUserDto("Alice", "[email protected]");
// Panache persist() 為 void — 使用 doNothing + verify
doNothing().when(userRepository).persist(any(User.class));
User result = userService.create(dto);
assertThat(result.name).isEqualTo("Alice");
verify(userRepository).persist(any(User.class));
}
}
整合測試
使用真實資料庫(Testcontainers)進行測試:
@QuarkusTest
@QuarkusTestResource(PostgresTestResource.class)
class UserRepositoryIntegrationTest {
@Inject
UserRepository userRepository;
@Test
@Transactional
void findByEmail_existingUser_returnsUser() {
User user = new User();
user.name = "Alice";
user.email = "[email protected]";
userRepository.persist(user);
Optional<User> found = userRepository.findByEmail("[email protected]");
assertThat(found).isPresent();
assertThat(found.get().name).isEqualTo("Alice");
}
}
API 測試
使用 REST Assured 測試 REST 端點:
@QuarkusTest
class UserResourceTest {
@Test
void createUser_validInput_returns201() {
given()
.contentType(ContentType.JSON)
.body("""
{"name": "Alice", "email": "[email protected]"}
""")
.when().post("/api/users")
.then()
.statusCode(201)
.body("name", equalTo("Alice"));
}
@Test
void createUser_invalidEmail_returns400() {
given()
.contentType(ContentType.JSON)
.body("""
{"name": "Alice", "email": "invalid"}
""")
.when().post("/api/users")
.then()
.statusCode(400);
}
}
覆蓋率報告
查看 target/site/jacoco/index.html 以取得詳細覆蓋率:
- 整體程式碼行覆蓋率(目標:80%+)
- 分支覆蓋率(目標:70%+)
- 找出未覆蓋的關鍵路徑
第四階段:安全掃描
相依套件弱點掃描 (Maven)
mvn org.owasp:dependency-check-maven:check
請檢視 target/dependency-check-report.html 中的 CVE。
Quarkus 安全審計
# 檢查有弱點的擴充套件
mvn quarkus:audit
# 列出所有擴充套件
mvn quarkus:list-extensions
OWASP ZAP(API 安全性測試)
docker run -t ghcr.io/zaproxy/zaproxy:stable zap-api-scan.py \
-t http://localhost:8080/q/openapi \
-f openapi
常見安全檢查
- 所有機密資訊儲存於環境變數(非程式碼中)
- 所有端點皆已完成輸入驗證
- 已設定身份驗證/授權
- CORS 已正確設定
- 已設定安全標頭
- 密碼使用 BCrypt 進行雜湊
- SQL 注入防護(使用參數化查詢)
- 公用端點已設定速率限制
第五階段:原生編譯
測試 GraalVM 原生映像檔相容性:
# 建置原生執行檔
mvn package -Dnative
# 或使用容器
mvn package -Dnative -Dquarkus.native.container-build=true
# 測試原生執行檔
./target/*-runner
# 執行基本煙霧測試
curl http://localhost:8080/q/health/live
curl http://localhost:8080/q/health/ready
原生映像檔問題排除
常見問題:
- 反射(Reflection):為動態類別新增反射設定
- 資源(Resources):使用
quarkus.native.resources.includes包含資源 - JNI:若使用原生函式庫,請註冊 JNI 類別
反射設定範例:
@RegisterForReflection(targets = {MyDynamicClass.class})
public class ReflectionConfiguration {}
第六階段:效能測試
使用 K6 進行負載測試
// load-test.js
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 50 },
{ duration: '1m', target: 100 },
{ duration: '30s', target: 0 },
],
};
export default function () {
const res = http.get('http://localhost:8080/api/markets');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 200ms': (r) => r.timings.duration < 200,
});
}
執行:
k6 run load-test.js
需監控的指標
- 回應時間(p50, p95, p99)
- 吞吐量(請求/秒)
- 錯誤率
- 記憶體使用量
- CPU 使用率
第七階段:健康檢查
# 存活檢查
curl http://localhost:8080/q/health/live
# 就緒檢查
curl http://localhost:8080/q/health/ready
# 所有健康檢查
curl http://localhost:8080/q/health
# 指標(若啟用)
curl http://localhost:8080/q/metrics
預期回應:
{
"status": "UP",
"checks": [
{
"name": "資料庫連線",
"status": "UP"
}
]
}
第八階段:容器映像檔建置
# 建置容器映像檔
mvn package -Dquarkus.container-image.build=true
# 或指定映像檔倉庫
mvn package \
-Dquarkus.container-image.build=true \
-Dquarkus.container-image.registry=docker.io \
-Dquarkus.container-image.group=myorg \
-Dquarkus.container-image.tag=1.0.0
# 測試容器
docker run -p 8080:8080 myorg/my-quarkus-app:1.0.0
容器安全掃描
# Trivy
trivy image myorg/my-quarkus-app:1.0.0
# Grype
grype myorg/my-quarkus-app:1.0.0
第九階段:設定驗證
# 檢查所有設定屬性
mvn quarkus:info
# 列出所有設定來源
curl http://localhost:8080/q/dev/io.quarkus.quarkus-vertx-http/config
環境特定檢查
- 資料庫 URL 已根據環境設定
- 機密資訊已外部化(Vault, 環境變數)
- 日誌層級設定適當
- CORS 起源設定正確
- 已設定速率限制
- 已啟用監控/追蹤
第十階段:文件審查
- OpenAPI/Swagger 文件是最新的 (
/q/swagger-ui) - README 包含設定說明
- API 變更已記錄
- 提供重大變更的遷移指南
- 設定屬性已記錄
產生 OpenAPI 規格:
curl http://localhost:8080/q/openapi -o openapi.json
驗證檢查清單
程式碼品質
- 建置通過且無警告
- 靜態分析乾淨(無高/中優先級問題)
- 程式碼遵循團隊慣例
- PR 中無註解掉的程式碼或待辦事項
測試
- 所有測試通過
- 程式碼覆蓋率 ≥ 80%
- 使用真實資料庫進行整合測試
- 安全測試通過
- 效能符合可接受範圍
安全性
- 無相依套件弱點
- 已測試身份驗證/授權
- 輸入驗證完整
- 機密資訊未儲存於原始碼中
- 已設定安全標頭
部署
- 原生編譯成功
- 容器映像檔可建置
- 健康檢查正確回應
- 設定對目標環境有效
原生映像檔
- 原生執行檔可建置
- 原生測試通過
- 啟動時間 < 100ms
- 記憶體使用量可接受
自動化驗證腳本
#!/bin/bash
set -e
echo "=== 第一階段:建置 ==="
mvn clean verify -DskipTests
echo "=== 第二階段:靜態分析 ==="
mvn checkstyle:check pmd:check spotbugs:check
echo "=== 第三階段:測試 + 覆蓋率 ==="
mvn test jacoco:report jacoco:check
echo "=== 第四階段:安全掃描 ==="
mvn org.owasp:dependency-check-maven:check
echo "=== 第五階段:原生編譯 ==="
mvn package -Dnative -Dquarkus.native.container-build=true
echo "=== 所有階段完成 ==="
echo "檢視報告:"
echo " - 覆蓋率:target/site/jacoco/index.html"
echo " - 安全性:target/dependency-check-report.html"
echo " - 原生執行檔:target/*-runner"
CI/CD 整合
GitHub Actions 範例
name: 驗證
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: 設定 JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
- name: 快取 Maven 套件
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: 建置
run: mvn clean verify -DskipTests
- name: 測試並產生覆蓋率
run: mvn test jacoco:report jacoco:check
- name: 安全掃描
run: mvn org.owasp:dependency-check-maven:check
- name: 上傳覆蓋率報告
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: target/site/jacoco/jacoco.xml
最佳做法
- 在每個 PR 前執行驗證循環
- 在 CI/CD 流程中自動化
- 立即修復問題;不要累積技術債務
- 保持覆蓋率高於 80%
- 定期更新相依套件
- 定期測試原生編譯
- 監控效能趨勢
- 記錄重大變更
- 審查安全掃描結果
- 為每個環境驗證設定