donghakim.dev — zsh
← ls ../blog

.dockerignore 한 줄이 mvn test 를 통째로 사라지게 했습니다

CodeBuild 에서 "No tests were executed!" 가 나오면 의존성·JUnit 보다 먼저 봐야 할 곳

.dockerignore 한 줄이 mvn test 를 통째로 사라지게 했습니다 · cover

한 줄 룰: Dockerfile 에 RUN mvn test ... 를 새로 추가했는데 CodeBuild 에서 No tests were executed! 가 나면, 컴파일과 의존성과 JUnit 버전을 의심하기 전에 .dockerignoresrc/test/ 부터 보세요.


시작

평소처럼 PR 을 올렸는데 CI 가 빨갛게 떴습니다. 깨진 단계는 docker build 의 테스트 실행 step 이었고, 로그 끝줄은 이렇게 찍혔습니다.

[ERROR] Failed to execute goal ... maven-surefire-plugin:2.22.2:test ...:
        No tests were executed!  (Set -DfailIfNoTests=false to ignore this error.)

이상한 점이 있었습니다. 로컬에서 ./mvnw test -Dtest='RedisServiceScanKeysTest' 는 5/5 통과합니다. pom.xml 의존성도 정상입니다. git push 도 잘 됐습니다. CodeBuild 만 실패하는데, 그 차이를 만들 만한 코드 변경은 새 테스트 클래스 한 개와 Dockerfile 의 RUN mvn test 단계 추가가 전부였습니다.

여기서 자연스러운 가설들이 쏟아집니다. POM 의 maven.test.skip 우선순위 문제일까. Surefire 가 JUnit 5 jupiter 엔진을 못 찾는 걸까. COPY --from=common /root/.m2 캐시에서 lettuce 가 빠진 걸까. 모두 그럴듯한데, 모두 아니었습니다.


결정적 로그 3줄

CodeBuild 로그를 위로 거슬러 올라가다 보면 결정적 시퀀스가 나옵니다.

[INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) ---
[INFO] skip non existing resourceDirectory /build/src/test/resources

[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) ---
[INFO] No sources to compile          ← src/test/java 자체가 컨테이너 안에 없음

[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) ---
[INFO] No tests to run.

핵심은 testCompile 단의 "No sources to compile" 입니다. 이게 보인다면 maven 입장에서 컴파일할 테스트 소스가 0개라는 뜻입니다. 즉, 빌드 컨텍스트에 src/test/ 자체가 들어오지 않았다는 신호입니다.

만약 컴파일 실패라면 Compilation failure 가 떴을 테고, 의존성 누락이면 Could not resolve dependencies ... 가 떴을 겁니다. "No sources to compile" 은 의미가 다릅니다. 파일이 처음부터 없다는 뜻이거든요.


범인

여러 백엔드 프로젝트가 공통으로 갖고 있는 .dockerignore 룰이 있었습니다. 이미지 크기와 빌드 시간 최적화를 위해 누군가 한 줄 넣어 둔 것입니다.

# Test
src/test/

docker build 는 이 패턴에 매칭되는 경로를 빌드 컨텍스트에서 통째로 제외합니다. 그래서 Dockerfile 의

COPY src ./src

가 실행되어도, 컨테이너 안의 /build/src/ 에는 src/main/ 만 들어가고 src/test/ 는 비어 있는 채로 들어옵니다. Maven 입장에선 테스트 소스가 0개이니 testCompile 출력이 없고, surefire 가 매칭 클래스를 못 찾고, No tests were executed! 로 빌드가 실패합니다 (-DfailIfNoTests=false 가 없으면 빌드 실패).

기존엔 모든 프로젝트가 mvn package -Dmaven.test.skip=true 로만 이미지를 빌드했기 때문에 이 룰이 문제가 안 됐습니다. 이번에 처음으로 Dockerfile 안에서 단위 테스트를 돌리기 시작했고, 그 순간 함정이 노출됐습니다.


픽스

.dockerignore 에서 src/test/ 한 줄 삭제로 끝났습니다.

 # Environment
 .env
 .env.*

-# Test
-src/test/
-
 # Others

영향은 두 가지입니다.

  • 테스트를 실행하지 않는 Dockerfile (개발용, 로컬용) 은 동작이 변하지 않습니다. 빌드 컨텍스트가 테스트 파일 수만큼 (보통 수십 KB) 커질 뿐입니다.
  • 테스트를 실행하는 Dockerfile 은 의도대로 mvn test 가 돕니다.

이미지 크기 최적화 측면에서도 문제가 없습니다. 어차피 multi-stage build 의 final stage 에는 컴파일된 jar 만 들어가지 테스트 클래스가 같이 가지 않거든요.


헷갈리기 쉬운 가설들 (이번 케이스에선 아니었음)

처음에 이 에러를 보면 다음 가설들이 더 자연스러워 보입니다. 시간을 가장 많이 빼앗기는 것들이라 적어 둡니다. 위의 "No sources to compile" 3줄 시퀀스가 결정적이라, 그게 보이면 모두 빠르게 배제할 수 있습니다.

  1. POM 의 <maven.test.skip>true</maven.test.skip> 과 CLI -Dmaven.test.skip=false 의 우선순위 충돌. Maven 3.8.x 에서 실제로 가끔 생기는 버그입니다. 다만 이 경우엔 testCompile 단에 "No sources to compile" 이 명확하게 찍혀 있어서 우선순위 문제가 아닙니다 (우선순위가 깨졌다면 surefire 자체가 안 호출됩니다).
  2. Surefire 2.22.2 가 JUnit 5 jupiter 엔진을 못 인식. spring-boot-starter-test 가 jupiter-engine 을 transitive 로 갖고 오므로 보통 인식됩니다. 만약 이게 원인이면 로그에 T E S T S 헤더 자체가 안 나옵니다. 우리 케이스는 testCompile 단부터 결과가 0이라 surefire 까지 가지도 못합니다.
  3. COPY --from=common /root/.m2 캐시에 의존성이 빠짐. CodeBuild 가 maven central 에 접근할 수 있으면 누락된 의존성은 실시간으로 받아 옵니다. 로그 앞부분에 Downloaded from central: ... 가 보이면 이 가설은 배제됩니다.

세 가설 모두 검증에는 5분 이상 걸리는데, "No sources to compile" 한 줄만 보면 1분 안에 진짜 원인으로 갈 수 있다는 게 이 글의 절약 포인트입니다.


재현 / 검증

로컬에서는 .dockerignore 가 적용되지 않습니다. 그래서 ./mvnw test 는 어떤 상황에서도 통과합니다 (직접 확인하니 5/5 통과였습니다). 재현하려면 로컬에서도 docker build -f Dockerfile.prod . 로 실제 컨테이너 빌드를 돌려 봐야 합니다.

"로컬에서는 멀쩡한데 CI 에서만 깨진다" 는 류의 사건은 거의 대부분 로컬과 CI 가 본 입력이 다르다 가 원인입니다. .dockerignore, .gitignore, 환경변수, Maven settings, JDK 마이너 버전 같은 것들. 이번엔 그 후보 중 가장 첫 번째에 해당했습니다.


어디서 다시 보일 수 있나

다음과 같은 조합이면 같은 함정이 재발할 수 있습니다.

  • .dockerignoresrc/test/ 류 패턴이 들어 있다.
  • Dockerfile 빌드 안에서 처음으로 mvn test (혹은 gradle test) 를 돌리려고 한다.
  • 그동안은 mvn package -DskipTests 로만 이미지를 빌드했었다.

위 셋이 겹치는 프로젝트가 둘 이상이라면, 첫 번째 프로젝트에서 부딪힌 뒤 두 번째 프로젝트도 비슷한 PR 에서 같은 에러를 만나기 쉽습니다. 처음 한 번 본 사람은 "1분 컷" 으로 끝나지만, 처음 보는 사람한테는 한 시간을 빼앗는 종류의 함정이라 이 글이 누군가의 한 시간을 줄이는 데 쓰이면 좋겠습니다.

#ci#codebuild#docker#dockerignore#maven#surefire#troubleshooting