본문 바로가기
빌드노트 검색

빌드노트

테스트가 실패했는데 Maven은 BUILD SUCCESS다

Surefire XML에 실패가 남아도 testFailureIgnore가 켜져 있으면 Maven은 종료 코드 0을 반환할 수 있다. 같은 실패 테스트로 기본값·true·false를 비교하고, 명령행 false와 POM 직접 설정이 충돌하는 경우까지 확인해 CI에 실패를 전달한다.

Surefire 로그에 Tests run: 1, Failures: 1이 있는데 마지막에는 BUILD SUCCESS가 찍히고 CI도 성공한다면, 먼저 테스트 실패를 무시하도록 설정했는지 확인한다. Surefire의 testFailureIgnore=true는 테스트를 실행하고 실패 보고서도 만들지만, 그 테스트 실패 때문에 Maven 빌드를 실패시키지는 않는다.

명령행에서 대응하는 이름은 maven.test.failure.ignore다. skipTests와는 역할이 다르다. 이번 글은 테스트를 찾지 못했거나 실행을 생략한 경우가 아니라, 실제로 발생한 단언 실패가 최종 성공으로 허용되는 경우를 다룬다.

먼저 실패 무시 옵션을 끈 채 실행한다

CI가 실행한 명령에서 -Dmaven.test.failure.ignore=true를 찾는다. 이 옵션이 있다면 제거하거나 false로 바꾼다. pom.xml의 properties, 부모 POM과 활성 프로필에도 같은 속성이 있을 수 있다. Surefire 3.5.4 매개변수 문서 (새 탭에서 열림)에 따르면 testFailureIgnore의 기본값은 false다. 기본 동작을 쓰는 프로젝트에 실패 무시 옵션을 추가할 필요는 없다.

JDK와 Maven이 준비된 Windows PowerShell에서, 문제가 생긴 모듈의 pom.xml이 있는 디렉터리로 이동해 다음처럼 확인한다. CI가 쓰는 프로필이 있다면 같은 프로필을 지정한다. clean은 이전 보고서와 이번 결과를 섞지 않기 위한 것이며 target 아래 기존 빌드 산출물을 지운다.

mvn.cmd -B "-Dmaven.test.failure.ignore=false" clean test
$mavenExit = $LASTEXITCODE
"Maven exit=$mavenExit"

아직 실패하는 테스트가 있다면 기대하는 변화는 테스트 통과가 아니라 BUILD FAILURE와 0이 아닌 종료 코드다. 아래 실험에서는 종료 코드 1이었다. 실행 직후 코드를 보관한 이유는 뒤의 보고서 조회 결과와 Maven의 결과를 구분하기 위해서다. Linux나 macOS에서는 mvn.cmd 대신 mvn을 쓰고 해당 셸의 방식으로 종료 코드를 확인한다. 이 글의 실제 명령 검증은 Windows에서 했다.

이번 실행의 보고서도 함께 읽는다. 다음은 실험에 쓴 lab.FailureProbeTest의 XML을 읽는 명령이다. 자신의 테스트 보고서 이름으로 바꾸며, 보고서 디렉터리를 별도로 지정했다면 그 위치를 쓴다. 기본 위치는 target/surefire-reports다.

[xml]$report = Get-Content './target/surefire-reports/TEST-lab.FailureProbeTest.xml' -Raw
'tests={0}, failures={1}, errors={2}, skipped={3}' -f $report.testsuite.tests, $report.testsuite.failures, $report.testsuite.errors, $report.testsuite.skipped

실험의 출력은 tests=1, failures=1, errors=0, skipped=0이었다. 실패 1건이 이번 실행에서 확인됐는데도 Maven이 0으로 끝난다면 아래의 플러그인 설정까지 확인한다. XML 자체가 없다면 테스트가 실행됐다고 가정할 수 없다. 먼저 로그에서 컴파일 실패, 실행 생략, 테스트 선택 결과와 실제 보고서 경로를 확인해야 한다.

같은 실패 테스트에서 종료 코드만 달라졌다

이 차이를 확인하려고 업무 코드와 분리한 새 Maven 프로젝트를 만들었다. Windows 11, Maven 3.9.16, Java 17.0.2에서 Compiler Plugin 3.14.1, Surefire 3.5.4, JUnit 4.13.2를 사용했다. FailureProbeTest.alwaysFails는 실행 흔적을 파일에 쓰고 PROBE_EXECUTED를 출력한 다음, Assert.fail로 의도적으로 실패한다. 실제 CI 장애 기록이 아니라 옵션의 영향을 비교한 실험이다.

테스트 소스는 바꾸지 않고 다음 세 명령을 각각 실행했다. 매번 clean부터 시작했고, 다음 실행 전에 로그와 XML을 따로 보존했다.

mvn.cmd -B clean test
mvn.cmd -B "-Dmaven.test.failure.ignore=true" clean test
mvn.cmd -B "-Dmaven.test.failure.ignore=false" clean test
  • 기본값: Maven 종료 코드 1, BUILD FAILURE.
  • 실패 무시 true: Maven 종료 코드 0, BUILD SUCCESS.
  • 명시적 false: Maven 종료 코드 1, BUILD FAILURE.

세 경우 모두 XML의 tests=1, failures=1, errors=0, skipped=0이 같았다. 테스트 본문이 쓴 흔적 파일과 출력도 모두 남았다. true를 주었을 때 실패가 사라지거나 테스트가 빠진 것은 아니었다. 다음은 그 실행의 로그에서 필요한 줄만 뽑은 것이다.

PROBE_EXECUTED
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

로그의 [ERROR]를 세는 것과 프로세스 종료 코드를 확인하는 것은 다르다. 이 실험처럼 Maven 자체가 0을 반환하면, 종료 코드만 기준으로 삼는 CI는 성공으로 처리할 수 있다. 이는 실험 결과에서 가능한 CI 동작을 설명한 것이며 실제 CI 서비스에 작업을 올려 검증한 결과는 아니다.

비교를 위해 같은 테스트에 -DskipTests=true도 적용했다. 이번에는 종료 코드 0이었지만 테스트 본문의 파일과 XML 보고서는 생기지 않았고, 컴파일된 테스트 클래스만 있었다. 공식 스킵 문서 (새 탭에서 열림)는 skipTests를 실행 생략으로, maven.test.skip을 테스트 컴파일까지 생략할 때 쓰는 속성으로 설명한다. 실패를 빌드에 반영하려는 상황에서 스킵으로 바꾸면 확인하려던 테스트 자체가 빠진다.

false를 줬는데도 성공하면 POM의 직접 설정을 본다

명령행에 false가 보인다는 이유만으로 실제 플러그인 값까지 false라고 판단하면 안 된다. 같은 실험 프로젝트의 Surefire configuration에 다음처럼 값을 직접 넣었더니 결과가 달라졌다.

<testFailureIgnore>true</testFailureIgnore>

이 상태에서는 -Dmaven.test.failure.ignore=false를 붙여도 실패 1건과 종료 코드 0이 함께 나왔다. 속성 조회인 help:evaluate는 maven.test.failure.ignore를 false로 보여 줬지만, 디버그 로그의 실제 주입 값은 다음과 같았다.

[DEBUG]   (s) testFailureIgnore = true

반대로 POM의 properties에만 maven.test.failure.ignore=true를 넣은 비교에서는 명령행 false가 적용돼 종료 코드 1이 나왔다. 속성 값과 플러그인의 직접 설정을 따로 확인해야 하는 이유다. 이 우선순위 관찰은 위에 명시한 Maven·Surefire 버전에서 확인했다.

설정 출처를 찾으려면 CI와 같은 프로필·속성으로 effective POM을 만든다. 다음 명령의 false도 CI에 적용하려는 값과 맞춘다. Help Plugin 3.5.1의 verbose는 각 설정이 어디에서 왔는지 주석을 덧붙인다. effective-pom 공식 설명 (새 탭에서 열림)

mvn.cmd -B "-Dmaven.test.failure.ignore=false" org.apache.maven.plugins:maven-help-plugin:3.5.1:effective-pom "-Dverbose=true" "-Doutput=effective-pom.xml"

생성된 파일에서 maven-surefire-plugin의 configuration과 실제 실행에 해당하는 execution을 찾고, testFailureIgnore와 출처 주석을 읽는다. 실험의 직접 설정은 여기에도 true로 남았다. 프로젝트에 따라 부모 POM이나 활성 프로필에서 온 값을 고쳐야 한다. effective POM은 병합된 설정을 보여 주는 자료이며, 모든 플러그인 기본값과 실행 시 주입 값을 나열하는 자료는 아니다. 이 실험에서도 직접 설정이 없는 기본 POM에는 testFailureIgnore가 표시되지 않았다.

실패를 무시할 이유가 없다면 원본 설정의 true를 제거하거나 false로 바꾼다. 위의 직접 설정을 false로 바꾼 뒤 같은 테스트를 실행하자 XML의 실패 수는 여전히 1이고 Maven 종료 코드는 1로 돌아왔다. 명령행 속성으로 조절해야 하는 구성이라면 직접 상수를 적는 대신 그 속성을 참조하도록 구성해야 한다. Maven 플러그인 설정 안내 (새 탭에서 열림)는 플러그인 매개변수 이름과 명령행 속성 이름이 다를 수 있다는 점도 설명한다. 이 옵션은 POM에서는 testFailureIgnore, 명령행에서는 maven.test.failure.ignore다.

CI에는 복원한 Maven 종료 코드를 전달한다

수정 뒤에는 실제로 실패하는 테스트의 이번 XML과 Maven 종료 코드를 다시 대조한다. 실패가 남아 있는 동안 Maven도 실패해야 한다. 테스트 0개로 얻은 성공이나 이전 실행의 XML로 이 확인을 대신할 수는 없다.

PowerShell CI 스크립트가 Maven 실행 뒤 보고서를 읽고 끝난다면, 마지막에는 앞에서 보관한 값을 exit $mavenExit로 전달한다. Maven이 이미 1을 반환했는데 CI만 성공한다면 이 전달 경로와 CI의 실패 허용 설정을 확인할 차례다. Surefire 설정을 계속 바꿀 상황은 아니다.

여기서 검증한 범위는 단일 모듈의 Surefire 테스트 단언 실패다. 다중 모듈 실행, Failsafe 통합 테스트, JVM 비정상 종료, 실제 CI 서비스의 상태 판정은 검증하지 않았다. testFailureIgnore=false 하나가 모든 종류의 성공 처리를 막는다고 일반화하지 말고, 사용 중인 Surefire 버전·이번 보고서·실제 Maven 종료 코드를 함께 확인한다.

← 전체 글 보기