무엇을 하고 싶었나
여러 작업이 폴더를 나눠 병렬로 돌아가는데, 푸시 직전 훅과 PR CI가 매번 수백 개 파일의 단위 테스트를 전부 돌려서 같은 일이 반복됐어요. 이걸 "바뀐 것과 관련된 테스트만"으로 줄이되, 안전장치는 그대로 두고 싶었어요.
미리 알면 좋은 개념이에요.
- import 그래프: 파일이 다른 파일을
import로 가져다 쓰는 관계의 지도예요. 바뀐 파일에서 이 지도를 거꾸로 따라가면 영향받는 테스트를 찾을 수 있어요. - fail closed: 판단이 불확실하면 통과시키지 않고 더 안전한 쪽(여기서는 전체 테스트)으로 가는 설계예요. 신호등이 고장 나면 모든 방향을 빨간불로 바꾸는 것과 같아요.
무엇을 했나
직접 도구를 만들지 않고 Vitest가 내장한 --changed <기준> 옵션을 썼어요. 커밋 차이, 스테이징·미스테이징, 추적되지 않는 파일까지 모아서 관련 테스트를 골라 줘요.
- 기준점은 추측하지 않아요. PR CI는 플랫폼이 알려 주는 실제 base 커밋을, 푸시 훅은 Git이 훅에 넘겨 주는 "그 브랜치의 이전 원격 SHA"를 써요. 새 브랜치는 Git이 갈라진 원본을 기록하지 않아서
TEST_BASE를 사람이 명시하게 했어요. - 불확실하면 전체로 가요. 파일 삭제·이름 변경은 그래프에서 출발점이 사라지니 전체 테스트로 바꾸고, 설정·lockfile·tsconfig·번역 JSON·공용 자산 같은 전역 입력은
forceRerunTriggers로 전부 다시 돌려요. 더러운 작업 트리나 현재 HEAD가 아닌 ref는 막아요. - 최종 후보는 별도 게이트로 뒀어요. 개발 브랜치로 가는 PR은 선택 실행, 메인 후보는 전체 + E2E예요.
- worker 수는 직접 재 보고 정했어요. 한 프로세스의 worker를 늘리는 것과 프로세스를 여러 개 동시에 띄우는 것은 달랐어요. 다만 실제 병렬 환경의 처리량은 측정하지 못해서 일반화하지 않았어요.
- 훅의 회귀 테스트(푸시 범위, 첫 푸시 기준, 더러운 작업 트리, 없는 base, 삭제 파일 폴백)를 추가했어요.
무엇을 배웠나
줄이는 규칙만으로는 위험해요. 그래프를 못 믿는 상황을 전체 실행으로 보내는 규칙이 같이 있어야 해요. 공유 모듈을 고치면 관련 테스트가 전체에 가까워서 선택 실행의 이득이 사라지는 것도 직접 확인했어요. → 변경 영향 기반 테스트 선택
기준점은 도구가 아니라 상황에서 받아요. 아는 값이 없으면 추측하지 않고 사람에게 요구해요.
병렬 환경에서만 흔들리는 E2E는 환경 문제부터 의심해요. 로컬 병렬 실행에서는 실패 위치가 바뀌며 흔들렸는데, 실패한 파일만 따로 돌리면 통과했고 CI와 같은 설정으로 돌리면 전부 통과했어요. sleep을 넣거나 앱 코드를 바꾸지 않고 CI 설정을 그대로 쓰는 쪽을 택했어요.
아직 확인하지 못한 것: 실제 병렬 작업 전체의 소요 시간과 자원 사용 개선, 동적 import 때문에 테스트가 누락되는 사례예요. 후자는 최종 전체 게이트에 맡겨 두었어요.