Quarkus 검증 루프
PR 전, 주요 변경 후, 배포 전에 실행하세요.
활성화 시점
- Quarkus 서비스의 풀 리퀘스트를 열기 전
- 주요 리팩토링 또는 종속성 업그레이드 후
- 스테이징 또는 프로덕션 배포 전 검증
- 전체 빌드 → 린트 → 테스트 → 보안 스캔 → 네이티브 컴파일 파이프라인 실행
- 테스트 커버리지 임계값(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}
처리해야 할 일반적인 문제
- 사용하지 않는 임포트 또는 변수
- 복잡한 메서드 (높은 순환 복잡도)
- 잠재적인 널 포인터 역참조
- 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단계: 헬스 체크
# 활성 상태 (Liveness)
curl http://localhost:8080/q/health/live
# 준비 상태 (Readiness)
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, env vars)
- 로깅 레벨 적절함
- 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% 이상 유지
- 정기적으로 종속성 업데이트
- 주기적으로 네이티브 컴파일 테스트
- 성능 추세 모니터링
- 주요 변경 사항 문서화
- 보안 스캔 결과 검토
- 각 환경에 맞게 설정 검증