Quarkus 検証ループ は何をしますか?
PR 前、大きな変更後、デプロイ前に実行します。
実行タイミング
- Quarkus サービスのプルリクエストを作成する前
- 大規模なリファクタリングや依存関係のアップグレード後
- ステージング、または本番環境へのデプロイ前検証
- 完全ビルド → lint → テスト → セキュリティスキャン → ネイティブコンパイル パイプラインの実行
- テストカバレッジがしきい値(80% 以上)を満たしていることの確認
- ネイティブイメージの互換性テスト
フェーズ 1: ビルド
# Maven
mvn clean verify -DskipTests
# Gradle
./gradlew clean assemble -x test
ビルドが失敗した場合は停止し、コンパイルエラーを修正します。
フェーズ 2: 静的解析
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}
対処すべき一般的な問題
- 未使用のインポートや変数
- 複雑なメソッド(高い循環的複雑度)
- 潜在的な null ポインタデリファレンス
- SpotBugs によってフラグが立てられたセキュリティ問題
フェーズ 3: テスト + カバレッジ
# 全テストの実行
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% 以上)
- 未カバーの重要なパスの特定
フェーズ 4: セキュリティスキャン
依存関係の脆弱性(Maven)
mvn org.owasp:dependency-check-maven:check
CVE については target/dependency-check-report.html を確認します。
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 インジェクション対策(パラメータ化クエリ)
- 公開エンドポイントへのレート制限
フェーズ 5: ネイティブコンパイル
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
ネイティブイメージのトラブルシューティング
よくある問題:
- リフレクション: 動的クラス用のリフレクション設定を追加
- リソース:
quarkus.native.resources.includesでリソースを含める - JNI: ネイティブライブラリを使用する場合は JNI クラスを登録
リフレクション設定の例:
@RegisterForReflection(targets = {MyDynamicClass.class})
public class ReflectionConfiguration {}
フェーズ 6: パフォーマンステスト
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 使用率
フェーズ 7: ヘルスチェック
# 生存確認
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": "Database connection",
"status": "UP"
}
]
}
フェーズ 8: コンテナイメージビルド
# コンテナイメージのビルド
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
フェーズ 9: 設定検証
# 全設定プロパティの確認
mvn quarkus:info
# 全設定ソースの一覧表示
curl http://localhost:8080/q/dev/io.quarkus.quarkus-vertx-http/config
環境固有のチェック
- データベース URL が環境ごとに設定されている
- シークレットが外部化されている(Vault、環境変数)
- ログレベルが適切
- CORS のオリジンが正しく設定されている
- レート制限が設定されている
- モニタリング/トレーシングが有効
フェーズ 10: ドキュメントレビュー
- OpenAPI/Swagger ドキュメントが最新(
/q/swagger-ui) - README にセットアップ手順が記載されている
- API の変更が文書化されている
- 破壊的変更の移行ガイドがある
- 設定プロパティが文書化されている
OpenAPI 仕様の生成:
curl http://localhost:8080/q/openapi -o openapi.json
検証チェックリスト
コード品質
- ビルドが警告なく成功する
- 静的解析がクリーン(高/中レベルの問題なし)
- コードがチームの規約に従っている
- PR にコメントアウトされたコードや TODO がない
テスト
- すべてのテストがパスする
- コードカバレッジが 80% 以上
- 実際のデータベースを使用した統合テスト
- セキュリティテストがパスする
- パフォーマンスが許容範囲内
セキュリティ
- 依存関係の脆弱性がない
- 認証/認可がテストされている
- 入力検証が完了している
- シークレットがソースコードに含まれていない
- セキュリティヘッダーが設定されている
デプロイ
- ネイティブコンパイルが成功
- コンテナイメージがビルドされる
- ヘルスチェックが正しく応答する
- 設定がターゲット環境に対して有効
ネイティブイメージ
- ネイティブ実行可能ファイルがビルドされる
- ネイティブテストがパスする
- 起動時間が 100ms 未満
- メモリフットプリントが許容範囲内
自動検証スクリプト
#!/bin/bash
set -e
echo "=== フェーズ 1: ビルド ==="
mvn clean verify -DskipTests
echo "=== フェーズ 2: 静的解析 ==="
mvn checkstyle:check pmd:check spotbugs:check
echo "=== フェーズ 3: テスト + カバレッジ ==="
mvn test jacoco:report jacoco:check
echo "=== フェーズ 4: セキュリティスキャン ==="
mvn org.owasp:dependency-check-maven:check
echo "=== フェーズ 5: ネイティブコンパイル ==="
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: Verification
on: [push, pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
- name: Build
run: mvn clean verify -DskipTests
- name: Test with Coverage
run: mvn test jacoco:report jacoco:check
- name: Security Scan
run: mvn org.owasp:dependency-check-maven:check
- name: Upload Coverage
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: target/site/jacoco/jacoco.xml
ベストプラクティス
- すべての PR の前に検証ループを実行する
- CI/CD パイプラインで自動化する
- 問題はすぐに修正し、負債を蓄積しない
- カバレッジを 80% 以上に保つ
- 依存関係を定期的に更新する
- 定期的にネイティブコンパイルをテストする
- パフォーマンスの傾向を監視する
- 破壊的変更を文書化する
- セキュリティスキャンの結果を確認する
- 各環境の設定を検証する