.dockerignore 한 줄이 mvn test 를 통째로 사라지게 했습니다
CodeBuild 에서 "No tests were executed!" 가 나오면 의존성·JUnit 보다 먼저 봐야 할 곳

한 줄 룰: Dockerfile 에
RUN mvn test ...를 새로 추가했는데 CodeBuild 에서No tests were executed!가 나면, 컴파일과 의존성과 JUnit 버전을 의심하기 전에.dockerignore의src/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줄 시퀀스가 결정적이라, 그게 보이면 모두 빠르게 배제할 수 있습니다.
- POM 의
<maven.test.skip>true</maven.test.skip>과 CLI-Dmaven.test.skip=false의 우선순위 충돌. Maven 3.8.x 에서 실제로 가끔 생기는 버그입니다. 다만 이 경우엔 testCompile 단에 "No sources to compile" 이 명확하게 찍혀 있어서 우선순위 문제가 아닙니다 (우선순위가 깨졌다면 surefire 자체가 안 호출됩니다). - Surefire 2.22.2 가 JUnit 5 jupiter 엔진을 못 인식. spring-boot-starter-test 가 jupiter-engine 을 transitive 로 갖고 오므로 보통 인식됩니다. 만약 이게 원인이면 로그에
T E S T S헤더 자체가 안 나옵니다. 우리 케이스는 testCompile 단부터 결과가 0이라 surefire 까지 가지도 못합니다. 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 마이너 버전 같은 것들. 이번엔 그 후보 중 가장 첫 번째에 해당했습니다.
어디서 다시 보일 수 있나
다음과 같은 조합이면 같은 함정이 재발할 수 있습니다.
.dockerignore에src/test/류 패턴이 들어 있다.- Dockerfile 빌드 안에서 처음으로
mvn test(혹은gradle test) 를 돌리려고 한다. - 그동안은
mvn package -DskipTests로만 이미지를 빌드했었다.
위 셋이 겹치는 프로젝트가 둘 이상이라면, 첫 번째 프로젝트에서 부딪힌 뒤 두 번째 프로젝트도 비슷한 PR 에서 같은 에러를 만나기 쉽습니다. 처음 한 번 본 사람은 "1분 컷" 으로 끝나지만, 처음 보는 사람한테는 한 시간을 빼앗는 종류의 함정이라 이 글이 누군가의 한 시간을 줄이는 데 쓰이면 좋겠습니다.