> ## Content Index
> Fetch the complete content index at: https://zerodraftlab.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# CI/CD는 테스트를 돌리는 일이 아니라 통합을 설계하는 일이다
- URL: https://zerodraftlab.com/test-pyramid-ci-cost/
- Published: 2026-07-20T14:19:02.000Z
- Updated: 2026-09-15T08:35:31.000Z
- Author: JooMong

PR이 초록이면 안심한다. GitLab MR의 파이프라인이 통과해도 일단 다음 일로 넘어간다. 그런데 main에 합쳐진 뒤 통합 테스트가 깨지거나, 배포 태그에서 smoke가 실패하거나, 같은 커밋에 CI가 두 번씩 붙어 비용이 불어나면 질문이 달라진다.

CI/CD는 테스트를 많이 돌리는 장치가 아니다. 변경이 다음 상태로 넘어갈 자격을 증명하는 흐름이다. PR과 MR은 코드를 제안하는 통합 지점이고, CI는 그 제안에 증거를 붙이며, CD는 증거가 쌓인 결과를 다음 환경으로 승격한다.

GitHub Actions와 GitLab CI/CD의 YAML 문법은 다르다. GitHub에서는 workflow와 event를 쓰고, GitLab에서는 pipeline source와 rules를 쓴다. 하지만 둘 다 같은 질문을 품고 있다. 이 검사는 어느 변경에서, 어느 통합 상태를 대상으로, 얼마의 비용으로 실행되어야 하는가.

## 테스트 피라미드는 CI/CD의 시간표다

Mike Cohn의 테스트 피라미드는 단위 테스트를 많이 쓰고 통합 테스트를 그보다 적게 쓰며 E2E 테스트는 더 적게 두자는 그림으로 알려졌다. Ham Vocke는 [The Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html?ref=zerodraftlab.com)에서 이 개념을 실제 테스트 포트폴리오로 풀었고, Martin Fowler도 [TestPyramid](https://martinfowler.com/bliki/TestPyramid.html?ref=zerodraftlab.com)을 자동화 테스트의 균형을 생각하는 은유로 설명했다.

핵심은 테스트 개수의 비율이 아니다. 아래층은 빠르고 실패 원인을 좁게 알려주며, 위층은 실제 환경을 넓게 확인하는 대신 느리고 실패 해석에 더 많은 맥락을 요구한다는 차이다. 둘은 모두 필요하지만 같은 이벤트에 같은 빈도로 붙을 필요가 없다.

Mike Wacker는 Google Testing Blog의 [Just Say No to More End-to-End Tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html?ref=zerodraftlab.com)에서 E2E 중심 전략의 문제를 설명했다. 제품을 빌드하고 테스트 환경에 배포한 뒤 브라우저로 전체 시나리오를 지나야 결과를 얻을 수 있다. 실패가 나와도 원인이 어느 서비스와 어느 경계에 있는지 바로 알기 어렵다.

그의 결론은 E2E를 없애자는 것이 아니다. 같은 결함을 더 작고 빠른 테스트로 잡을 수 있다면 굳이 전체 시스템을 통과시키지 말자는 것이다. 커밋의 피드백은 작은 테스트가 담당하고, 통합된 결과의 신뢰는 더 큰 테스트가 담당해야 한다.

[Software Engineering at Google 11장](https://abseil.io/resources/swe-book/html/ch11.html?ref=zerodraftlab.com)은 이 차이를 테스트의 크기와 범위로 나눈다. 무엇을 검증하는지가 scope라면, 몇 개의 프로세스와 머신과 네트워크와 시간이 필요한지가 size다. 한 함수만 확인해도 브라우저를 띄워야 한다면 큰 테스트일 수 있고, 여러 컴포넌트를 다뤄도 한 프로세스 안에서 끝나면 작은 테스트에 가까울 수 있다.

이 분류가 CI/CD의 시간표를 만든다. small은 변경마다 빠르게 실행하고, medium은 통합 경계에서 확인하며, large는 빌드·릴리스·배포 같은 상위 게이트에서 실행한다. 테스트가 좋은지 나쁜지만 결정하는 것이 아니라, 언제 실행해야 그 테스트의 정보 가치가 비용을 이기는지를 결정하는 셈이다.

## PR과 MR은 코드 저장소의 통합 계약이다

PR과 MR은 단순한 UI 이름 차이가 아니다. 둘 다 아직 기본 브랜치에 들어가지 않은 변경을 하나의 통합 단위로 묶고, 리뷰와 자동 검증과 승인을 모으는 경계다. 이 경계가 명확해야 CI의 결과도 의미를 갖는다.

PR의 초록 불빛은 “이 브랜치가 지금까지의 조건을 만족한다”는 증거다. 그것은 “다음 커밋에서도 안전하다”거나 “main에 들어간 뒤에도 모든 시스템과 맞는다”는 보증이 아니다. MR의 성공 파이프라인도 마찬가지다. 소스 브랜치만 검증했는지, 타깃 브랜치와 합쳐진 임시 결과까지 검증했는지에 따라 증거의 범위가 달라진다.

따라서 CI를 설계할 때 먼저 테스트 목록을 쓰면 안 된다. PR/MR에서 무엇을 약속할지, main에서 무엇을 통합할지, tag에서 무엇을 실제 배포의 증거로 삼을지 먼저 나눠야 한다. 테스트는 그 계약을 구현하는 수단이다.

## GitHub Actions의 이벤트는 실행 그래프다

GitHub Actions workflow는 어떤 event가 일어날 때 실행된다. [공식 문서](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows?ref=zerodraftlab.com)에서 \`pull\_request\`는 PR이 열리거나 소스 브랜치에 커밋이 추가되는 등의 활동을 기준으로 실행되고, \`push\`는 브랜치에 커밋이 올라가는 사건을 기준으로 실행된다.

이 둘을 한 workflow에 무심코 함께 걸면 같은 변경이 PR 레인과 브랜치 push 레인에서 모두 실행될 수 있다. GitHub는 여러 이벤트가 동시에 일어나면 여러 workflow run을 만들 수 있다고 설명한다. 기능적으로는 모두 초록인데, 비용과 대기 시간은 아무도 책임지지 않는 상태가 된다.

이벤트는 단순한 스위치가 아니라 실행 그래프다. \`pull\_request\`에는 리뷰 중인 변경의 빠른 검증을 붙이고, \`push\`의 main 조건에는 통합 결과의 풀 테스트를 붙이며, tag나 release에는 배포와 production smoke를 붙인다. merge queue를 쓰는 저장소라면 required check가 \`merge\_group\`에서도 보고되도록 별도 이벤트를 다뤄야 한다. 그렇지 않으면 PR에서는 통과한 체크가 큐에 들어간 뒤 다시 보고되지 않아 머지가 멈출 수 있다.

## GitLab MR pipeline은 브랜치와 병합 결과를 구분한다

GitLab의 [merge request pipeline](https://docs.gitlab.com/ci/pipelines/merge%5Frequest%5Fpipelines/?ref=zerodraftlab.com)은 MR을 만들거나 소스 브랜치에 새 커밋을 push할 때 실행할 수 있다. 기본 MR pipeline은 소스 브랜치의 내용만 실행한다. 타깃 브랜치와 실제로 합쳐졌을 때 문제가 없는지 보려면 merged results pipeline을 써야 한다. GitLab은 이 경우 두 브랜치를 합친 임시 커밋을 만들어 통합 결과를 검사한다.

여기서도 pipeline source가 계약의 일부가 된다. \`.gitlab-ci.yml\`의 \`rules\`나 \`workflow:rules\`가 \`CI\_PIPELINE\_SOURCE == "merge\_request\_event"\`를 명시해야 MR pipeline이 만들어진다. 반대로 branch pipeline과 MR pipeline을 모두 허용하는 규칙을 넓게 쓰면 같은 push에 파이프라인이 두 개 생길 수 있다.

GitLab 공식 troubleshooting 문서가 이 문제를 따로 다루는 이유가 있다. 중복 pipeline은 단순한 화면 중복이 아니다. 같은 커밋에 runner를 두 번 점유하고, 결과가 서로 다른 상태처럼 보이며, 어떤 pipeline을 머지 조건으로 삼아야 하는지 흐리게 만든다. GitHub의 \`pull\_request\`와 \`push\` 조합, GitLab의 branch와 MR pipeline 조합은 문법은 달라도 같은 설계 실패를 만들 수 있다.

이제 테스트 피라미드와 PR/MR의 관계가 보인다. 테스트의 층은 실행 비용을 설명하고, PR/MR은 검증 결과가 모일 통합 지점을 설명한다. CI/CD는 이 둘을 연결하는 시간표다.

그렇다면 이 시간표를 실제 비용 사고와 GitHub·GitLab의 레인 설계에 어떻게 적용해야 할까?

먼저 같은 테스트를 어느 플랫폼에서 실행할지보다, 어느 상태의 코드를 증명하는지부터 고정해야 한다. 소스 브랜치의 가능성을 확인하는 검증과 타깃 브랜치와 합쳐진 결과를 확인하는 검증과 실제 배포된 환경을 확인하는 검증은 서로 다른 증거다. 한 레인의 초록을 다른 레인의 초록으로 빌려 쓰면 CI는 빨라 보여도 신뢰 경계가 사라진다.

## 비용 사고는 테스트보다 통합 지점에서 시작됐다

2026년 7월 20일 relic-works org에서는 Actions가 7월 1일부터 20일까지 11,462분을 사용했고, 그중 oblivseoul-sveltekit이 9,907분을 차지했다. 조직 전체 예산 상한은 $50에 도달했고, \`prevent\_further\_usage\`가 켜진 예산은 다른 저장소의 잡 시작까지 막았다. 결제 실패처럼 보이는 오류였지만 실제 원인은 비용 상한이라는 통합 인프라 정책이었다.

이 수치는 2026년 7월 20일 당시의 시점 고정 스냅샷이다. 이후 같은 날 API를 다시 읽었을 때 조직 Actions 누적 사용량은 11,587.6667분, 해당 저장소는 10,030.6667분으로 늘었고, Actions 예산은 $100이며 \`prevent\_further\_usage=true\`였다. 비용 수치를 사용할 때는 숫자보다 관측 시점을 함께 보존해야 한다.

저장소의 구조는 더 선명했다. \`ci\`가 230회 실행되었고 한 번에 약 16.5분이 걸렸다. 그중 E2E 단계가 871초로 88%를 차지했다. 55개 스펙을 desktop과 mobile 두 뷰포트로 매번 돌렸고, Lighthouse도 229회 실행되어 601분을 사용했다. 느리고 넓은 검증이 하루 11\~12개의 에이전트 커밋마다 반복되면, 테스트는 안전망이 아니라 생산비가 된다.

GitHub Actions 공식 문서는 private repository의 hosted runner 사용량을 계정의 무료분과 과금분으로 계산하고 각 job의 분과 부분 분을 올림한다고 설명한다. 그래서 병렬 잡을 늘려 벽시계 시간을 줄이는 최적화와 총 과금 분을 줄이는 최적화는 다르다. 비용을 줄이려면 세 항, 즉 실행 빈도·잡 시간·잡 수 중 실제로 곱이 큰 항을 줄여야 한다.

## 세 플랫폼 레인은 사실 세 상태를 증명한다

oblivseoul-sveltekit의 [PR #307](https://github.com/relic-works/oblivseoul-sveltekit/pull/307?ref=zerodraftlab.com)은 GitHub Actions를 두 레인으로 나눴다. PR 커밋에서는 매출 경로와 컴플라이언스 가드와 계약 메타 스펙을 확인하는 8개 스모크를 남겼다. 리드폼 복구, 리드 스키마, 멱등성, CTA 링크, 리다이렉트, GA 호스트 가드가 여기에 들어간다.

main에 합쳐지면 풀 Playwright 스위트와 비주얼 골든을 돌린다. Lighthouse도 pull request 트리거를 제거하고 main push와 수동 실행으로 옮겼다. 이것은 테스트를 삭제한 것이 아니라, 통합 결과가 생긴 뒤에야 의미가 생기는 측정과 시각 회귀 검사를 더 적합한 문으로 옮긴 것이다.

같은 구조를 GitLab MR에 옮기면 MR pipeline에는 빠른 계약·정적 검사·핵심 스모크를 둔다. merged results pipeline을 선택한 경우에는 타깃 브랜치와 합쳐진 임시 결과에서 통합 테스트와 넓은 회귀를 확인할 수 있다. default branch pipeline은 통합된 main의 산출물을 만들고, tag pipeline은 실제 배포와 production smoke를 증명한다.

이것은 GitHub와 GitLab의 공식 기본 설정이 아니라, 같은 위험을 각 플랫폼의 이벤트 모델에 맞춰 배치한 설계다. GitHub에서는 \`pull\_request\`, \`push\`, 필요하면 \`merge\_group\`의 관계를 정리하고, GitLab에서는 \`CI\_PIPELINE\_SOURCE\`, \`workflow:rules\`, merged results의 관계를 정리한다. 플랫폼이 바뀌어도 상태의 순서는 바뀌지 않는다.

## 중복 pipeline은 CI/CD의 아이스크림 콘이다

테스트 피라미드가 E2E의 비율만 말하는 것은 아니었던 것처럼, CI 비용도 단순히 “잡이 오래 걸린다”의 문제가 아니다. 소스 브랜치와 MR 결과를 둘 다 검사하고, PR과 push를 둘 다 검사하고, main과 tag에서 같은 풀 스위트를 다시 검사하면 하나의 변경이 여러 증거처럼 복제된다.

GitLab은 \`workflow:rules\`로 어떤 종류의 pipeline을 만들지 먼저 제한하라고 안내한다. GitHub도 여러 이벤트가 동시에 발생하면 여러 run이 생길 수 있으므로 event와 branch filter를 의도적으로 설계해야 한다. 중복을 막는 일은 CI 최적화의 마지막 손질이 아니라, 어떤 결과가 통합을 허용하는지 결정하는 계약의 일부다.

여기서 Owner가 고정한 게이트는 비용과 무관하게 남아야 한다. PR마다 유지하기로 한 coverage-gate, 컴플라이언스 가드, 매출 경로 스모크는 작은 형태로라도 가장 빠른 통합 경계에 있어야 한다. 반대로 Lighthouse와 비주얼 골든처럼 통합 결과에서 한 번 확인해도 되는 검사는 머지 뒤로 미룰 수 있다. 줄이는 것은 방어선이 아니라 중복과 잘못된 타이밍이다.

## CI는 증거를 만들고 CD는 증거를 승격한다

CI와 CD를 하나의 거대한 pipeline으로 붙이면 모든 단계가 모든 변경에 반응해야 한다는 압박이 생긴다. 그러면 PR 하나가 사실상 배포 후보처럼 다뤄지고, 아직 설계가 바뀌는 코드에 가장 비싼 검증이 붙는다.

더 단순한 모델은 상태를 나누는 것이다. PR/MR은 변경이 리뷰 가능한지 증명한다. main은 여러 변경이 함께 작동하는지 증명한다. tag나 release는 이 통합 결과를 배포할 수 있는지 증명한다. production smoke는 실제 사용자 환경에서 방금 승격한 산출물이 맞는지 증명한다.

GitHub의 protected branch가 required status checks와 리뷰를 머지 조건으로 삼고, GitLab의 MR pipeline과 merged results가 머지 전 검증을 담당하는 이유도 이 경계 때문이다. 머지는 단순히 브랜치를 합치는 Git 명령이 아니라, 한 상태의 증거를 다음 상태의 책임으로 넘기는 행위다.

좋은 CI/CD는 가장 많은 테스트를 가장 빨리 돌리는 시스템이 아니다. 변경의 위험도가 올라가는 지점마다 그 위험에 맞는 증거를 하나씩 요구하는 시스템이다. 작은 검사는 커밋과 PR/MR에, 넓은 검사는 main과 merged result에, 배포 검사는 tag와 production에 둔다.

**PR과 MR은 코드를 보여주는 페이지가 아니라 통합 계약이고, CI는 초록불을 만드는 장치가 아니라 증거를 생산하는 공정이며, CD는 그 증거를 다음 환경으로 승격하는 책임이다.** 이 세 가지를 분리하면 GitHub Actions와 GitLab CI/CD는 경쟁 제품이 아니라 같은 운영 원리를 구현하는 서로 다른 표면으로 보인다.

---

주요 출처: Ham Vocke, [The Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html?ref=zerodraftlab.com); Martin Fowler, [TestPyramid](https://martinfowler.com/bliki/TestPyramid.html?ref=zerodraftlab.com); Mike Wacker, [Just Say No to More End-to-End Tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html?ref=zerodraftlab.com); Google, [Software Engineering at Google, Chapter 11: Testing Overview](https://abseil.io/resources/swe-book/html/ch11.html?ref=zerodraftlab.com); GitHub, [Events that trigger workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows?ref=zerodraftlab.com), [Triggering a workflow](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow?ref=zerodraftlab.com), [Managing protected branches](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches?ref=zerodraftlab.com); GitLab, [Merge request pipelines](https://docs.gitlab.com/ci/pipelines/merge%5Frequest%5Fpipelines/?ref=zerodraftlab.com), [Merged results pipelines](https://docs.gitlab.com/ci/pipelines/merged%5Fresults%5Fpipelines/?ref=zerodraftlab.com), [Troubleshooting merge request pipelines](https://docs.gitlab.com/ci/pipelines/mr%5Fpipeline%5Ftroubleshooting/?ref=zerodraftlab.com), [Specify when jobs run with rules](https://docs.gitlab.com/ci/jobs/job%5Frules/?ref=zerodraftlab.com); 사례: [oblivseoul-sveltekit PR #307](https://github.com/relic-works/oblivseoul-sveltekit/pull/307?ref=zerodraftlab.com). 11,462분·9,907분·$50·86%는 2026-07-20 시점 고정 스냅샷이며, 후속 API 수치는 별도로 구분했다.