Split View: 테스트 전략 완전 가이드: 피라미드 논쟁 대신 결정 기준
테스트 전략 완전 가이드: 피라미드 논쟁 대신 결정 기준
- 들어가며
- 1. 테스트가 사는 이유: 회귀 방어인가 설계 압력인가
- 2. 피라미드와 트로피 — 논쟁의 실제 축
- 3. '단위'라는 말이 합의된 적이 없다
- 4. 비용 함수로 보기: 작성·실행·유지·오탐
- 5. 무엇을 목(mock)으로 대체할 것인가
- 6. 커버리지 숫자를 다루는 법
- 7. 결함이 실제로 어디서 나오는가 — 데이터로 비율 정하기
- 8. 느리고 깨지는 테스트를 관리하는 운영 규칙
- 9. 팀의 테스트 전략을 한 장으로 쓰기
- 퀴즈: 실력을 확인해 보세요
- 마치며
- 참고 자료
- 이어서 읽기
들어가며
이 블로그에는 이미 테스트를 다룬 한국어 완전 가이드가 여러 편 있습니다. 소프트웨어 테스팅 전략 완전 가이드는 어떤 테스트 종류가 있고 어떻게 쓰는지를, 속성 기반 테스트 실전은 특정 기법을, 검증 설계 — 테스트를 신뢰 근거로 보기는 검증을 개인 역량 관점에서 다룹니다. 세 글 모두 어떤 테스트가 존재하고 어떻게 작성하는가에 관한 글입니다.
이 글은 다릅니다. 팀이 자기 비율을 스스로 정하는 방법을 다룹니다. 피라미드와 트로피 중 하나를 고르지 않고, 그 논쟁이 실제로는 어떤 축들 위에서 벌어지는지를 분해합니다. 축을 알면 "우리 스택에서는 어느 쪽인가"를 스스로 답할 수 있고, 그게 남의 결론을 복사하는 것보다 오래갑니다.
먼저 결론을 밝히면 이렇습니다. 비율은 답이 아니라 결과입니다. 결함이 어디서 나오는지, 통합 환경 구성이 얼마나 비싼지, E2E가 얼마나 자주 헛발질하는지를 측정하면 비율은 자동으로 도출됩니다.
1. 테스트가 사는 이유: 회귀 방어인가 설계 압력인가
테스트 논쟁이 겉돌 때는 대개 참여자들이 서로 다른 목적을 전제하고 있습니다. 테스트가 존재하는 이유는 크게 넷이고, 목적이 다르면 좋은 테스트의 모양도 달라집니다.
- 회귀 방어: 어제까지 되던 것이 오늘 깨졌는지 알려 줍니다. 이 목적에서는 테스트가 구현이 아니라 외부에서 관찰 가능한 행동에 붙어야 합니다. 리팩터링할 때 같이 고쳐야 하는 테스트는 회귀 방어 자산이 아니라 부채입니다.
- 설계 압력: 테스트하기 어려운 코드는 대개 결합이 심한 코드입니다. 먼저 테스트를 쓰면 인터페이스가 사용자 관점에서 다듬어집니다. 이 목적에서는 세밀한 단위 테스트가 유리하고, 테스트가 구현에 어느 정도 붙는 것도 감수합니다.
- 명세와 문서: 코드가 무엇을 보장하는지를 실행 가능한 형태로 남깁니다. 읽히는 것이 목적이라 이름과 배치가 중요합니다.
- 배포 신뢰: 사람이 확인하지 않고도 내보낼 수 있게 만듭니다. 여기서는 통합·계약·E2E 쪽 비중이 커집니다.
여기서 첫 번째 갈등이 보입니다. 회귀 방어를 중시하면 테스트를 구현에서 떼어내려 하고, 설계 압력을 중시하면 테스트를 구현 가까이 붙입니다. 같은 코드베이스에서 두 목적이 충돌하면 "이 테스트는 리팩터링 때 같이 고쳐도 되는가"라는 질문 하나로 정리할 수 있습니다. 회귀 방어용이면 안 되고, 설계 압력용이면 괜찮습니다. 팀이 이 구분에 이름을 붙여 두면 리뷰에서 논쟁이 절반으로 줄어듭니다.
2. 피라미드와 트로피 — 논쟁의 실제 축
2-1. 두 주장의 원문
테스트 피라미드는 Martin Fowler의 정리가 널리 인용됩니다. 핵심은 한 문장입니다. "Write lots of small and fast unit tests. Write some more coarse-grained tests and very few high-level tests that test your application from end to end." 위로 갈수록 느리고 비싸므로 수를 줄이라는 것입니다.
주목할 점은 같은 글이 자기 모델의 한계를 스스로 인정한다는 것입니다. "the concept of the test pyramid falls a little short if you take a closer look"이라고 적고, 중간층을 가리키는 "service test is a term that is hard to grasp"이라고 말하며, 최신 프런트엔드 환경에서는 "UI tests don't have to be on the highest level"이라고 덧붙입니다. 최상단 E2E에 대해서는 "notoriously flaky and often fail for unexpected and unforeseeable reasons"이며 "require a lot of maintenance and run pretty slowly"라고 평가합니다.
테스팅 트로피는 Kent C. Dodds의 정리입니다. 정적 검사, 단위, 통합, E2E의 네 층으로 나누고 통합 층을 가장 크게 잡습니다. 근거는 투자 대비 신뢰도이며, 그의 표현으로는 "The more your tests resemble the way your software is used, the more confidence they can give you"입니다.
2-2. 논쟁을 끝내려는 시도가 오히려 문제라는 지적
중요한 것은 트로피 쪽도 이 논쟁을 정답 찾기로 보지 않는다는 점입니다. Dodds는 같은 글에서 단위 테스트의 정의가 "24 different definitions of unit test"만큼 갈린다고 소개하고, "Any attempt to come to a single definition for all these terms is a futile endeavor"라고 씁니다. 그리고 Justin Searls의 말을 인용합니다. "People love debating what percentage of which type of tests to write, but it's a distraction."
Fowler 쪽도 마찬가지입니다. "If you ask three different people what 'unit' means in the context of unit tests, you'll probably receive four different, slightly nuanced answers."
즉 두 진영의 대표 문헌이 모두 "비율 논쟁은 본질이 아니다"라고 말하고 있습니다. 그런데도 현장의 논쟁이 끝나지 않는 이유는, 비율이라는 한 숫자 뒤에 서로 다른 여러 조건이 숨어 있기 때문입니다.
2-3. 실제 갈림 축 네 가지
축 1 '단위'의 경계를 어디로 잡는가
함수 하나 = 단위 → 단위 테스트 수가 폭증, 피라미드처럼 보임
모듈/기능 = 단위 → 같은 테스트가 '통합'으로 분류됨, 트로피처럼 보임
축 2 통합 환경 구성 비용이 얼마인가
컨테이너로 30초 → 통합 비중을 올리는 쪽이 합리적
전용 스테이징 필요 → 통합 비중을 올리면 피드백 주기가 무너짐
축 3 E2E 층의 오탐률이 얼마인가
1% 미만 → E2E를 신뢰 근거로 쓸 수 있음
10% 이상 → 신호가 아니라 소음, 층 자체를 줄여야 함
축 4 테스트의 주된 목적이 무엇인가
회귀 방어 → 행동 중심, 큰 단위, 목 최소화
설계 피드백 → 구현 근처, 작은 단위, 목 허용
네 축에 팀의 실제 값을 넣으면 비율은 논쟁 없이 나옵니다. 컨테이너 기반 통합 환경이 30초에 뜨고, E2E 오탐률이 8%이고, 팀의 주 목적이 회귀 방어라면 결과는 트로피에 가까운 모양이 됩니다. 반대로 도메인 로직이 두껍고 통합 환경이 비싸면 피라미드에 가까워집니다. 어느 쪽도 틀리지 않았고, 두 글이 서로 다른 축 값을 가진 코드베이스를 전제했을 뿐입니다.
2-4. 옮길 때 반드시 확인할 것
Dodds 본인도 트로피가 개별 코드베이스를 대상으로 한 모델이며 마이크로서비스나 서버리스 구성에 그대로 적용되지 않을 수 있다고 밝힙니다. 어떤 모델이든 원문이 전제한 맥락을 함께 옮겨야 합니다. 그림만 복사하면 남의 축 값으로 우리 비율을 정하는 셈이 됩니다.
3. '단위'라는 말이 합의된 적이 없다
3-1. 정의 스펙트럼
비율 논쟁의 절반은 용어 문제입니다. 같은 테스트가 팀에 따라 단위로도 통합으로도 분류됩니다.
좁음 ←──────────────────────────────────────────────→ 넓음
함수 하나 클래스 하나 모듈/기능 하나 프로세스 하나
협력자 전부 목 일부 목 내부는 실제 외부만 대역
DB는 인메모리 DB는 컨테이너
Fowler와 Dodds가 모두 지적하듯 이 스펙트럼 어디에도 표준 경계는 없습니다. 그래서 "단위 테스트 비중 70%"라는 목표는 경계를 정하기 전에는 아무 의미가 없습니다. 경계를 옮기는 것만으로 같은 코드베이스의 숫자가 20%에서 80%로 움직입니다.
3-2. 이름 대신 성질로 부르기
실무에서 잘 통하는 우회로는 분류 이름을 버리고 성질로 부르는 것입니다.
- 격리 수준: 이 테스트가 실제로 실행하는 코드의 범위는 어디까지인가.
- 실행 시간: 밀리초인가, 초인가, 분인가.
- 환경 요구: 프로세스 안에서 끝나는가, 외부 프로세스가 필요한가, 네트워크가 필요한가.
- 결정성: 같은 입력에 항상 같은 결과인가.
이 네 값이 정해지면 이름은 필요 없습니다. CI 파이프라인의 단계 구성도 이름이 아니라 이 값으로 나눕니다. "3초 안에 끝나고 외부 의존이 없는 테스트"는 커밋마다 돌리고, "컨테이너가 필요한 테스트"는 병합 전에 돌리고, "스테이징이 필요한 테스트"는 배포 파이프라인에서 돌립니다.
4. 비용 함수로 보기: 작성·실행·유지·오탐
테스트를 자산이 아니라 비용과 수익이 함께 있는 계약으로 보면 결정이 쉬워집니다. 비용은 네 항목입니다.
- 작성 비용: 한 번만 지불합니다. 가장 눈에 띄지만 총비용에서 차지하는 비중은 대체로 가장 작습니다.
- 실행 비용: 매 커밋마다 지불합니다. 하루 50번 도는 파이프라인에서 테스트 하나가 2초 느려지면 연간 수 시간이 사라집니다. 더 큰 손실은 대기 시간이 길어져 개발자가 컨텍스트를 잃는 것입니다.
- 유지 비용: 코드가 바뀔 때마다 지불합니다. 구현 세부에 붙은 테스트는 리팩터링마다 청구서를 보냅니다. 이 항목이 총비용의 대부분을 차지하는 경우가 흔합니다.
- 오탐 비용: 실패가 진짜인지 확인하는 시간, 그리고 신뢰를 잃는 비용입니다. 마지막 항목이 가장 비쌉니다. 빨간 파이프라인을 습관적으로 재실행하기 시작하면 그 테스트 스위트는 이미 자산이 아닙니다.
수익 항목은 하나입니다. 막아 낸 결함의 기댓값, 즉 그 테스트가 없었다면 프로덕션까지 갔을 결함의 확률과 비용의 곱입니다.
숫자를 넣어 보면 감이 잡힙니다. E2E 테스트 하나를 작성하는 데 4시간, 실행에 40초, 분기마다 두 번 손보는 데 2시간이 든다고 해 봅시다. 하루 30번 도는 파이프라인이면 실행만으로 연간 약 68시간이 사라지고, 유지 비용까지 더하면 1년 총비용은 80시간을 넘습니다. 같은 로직을 검증하는 단위 테스트 대여섯 개의 총비용은 그 절반에도 미치지 않는 경우가 많습니다. 작성 비용만 비교하면 E2E가 싸 보이지만, 총비용에서는 대개 반대입니다.
여기서 실무적으로 유용한 규칙이 나옵니다. 같은 결함을 두 층에서 잡고 있다면 비싼 쪽을 지웁니다. E2E가 검증하는 로직 분기를 단위 테스트가 이미 전부 덮고 있다면 그 E2E는 비용만 남습니다. E2E에는 층 간 연결(라우팅, 인증, 직렬화, 설정)처럼 아래층이 원리적으로 볼 수 없는 것만 남깁니다.
반대 방향의 실수도 흔합니다. 아래층으로 밀 수 없는 검증을 억지로 단위 테스트로 만들면, 목이 실제와 어긋난 순간 전부 초록인데 프로덕션이 깨집니다. 판단 기준은 "이 결함이 이 층에서 원리적으로 보이는가"이지 "이 층이 싼가"가 아닙니다.
5. 무엇을 목(mock)으로 대체할 것인가
목 사용 범위는 이 분야에서 가장 오래된 논쟁 중 하나이고, 여전히 합의가 없습니다.
5-1. 두 입장
- 목을 넓게 쓰는 쪽: 협력자를 대역으로 바꾸면 테스트가 빨라지고 실패 원인이 좁혀집니다. 실패한 테스트 이름만 보고도 어느 단위가 깨졌는지 압니다. 아직 존재하지 않는 협력자에 대해서도 먼저 설계할 수 있습니다.
- 목을 좁게 쓰는 쪽: 목은 내 가정을 검증하는 것이지 상대의 실제 동작을 검증하지 않습니다. 상대가 바뀌어도 목은 조용하므로, 전부 초록인데 프로덕션이 깨지는 상황이 만들어집니다. 또한 호출 순서와 인자를 검증하는 목은 구현에 강하게 붙어 리팩터링을 막습니다.
5-2. 갈림 축과 실무 기준
논쟁의 축은 대체 대상이 통제 가능한가입니다. 다음 기준이 대체로 잘 작동합니다.
목으로 대체한다 실제를 쓴다
───────────────────── ─────────────────────
외부 시스템 (결제, 메일) 같은 코드베이스 안의 협력자
비결정적 요소 (시각, 난수) 순수 계산과 값 객체
재현하기 어려운 실패 컨테이너로 띄울 수 있는 저장소
느린 I/O (수 초 이상) 인메모리로 대체 가능한 저장소
추가로 지킬 규칙 두 가지가 있습니다. 첫째, 내가 소유하지 않은 인터페이스는 직접 목으로 만들지 않습니다. 외부 SDK를 감싸는 얇은 어댑터를 만들고 그 어댑터를 대역으로 바꿉니다. 외부 SDK의 동작을 상상해 목으로 굳히면 그 상상이 틀렸을 때 아무 테스트도 실패하지 않습니다. 둘째, 목을 썼다면 그 목이 실제와 같다는 사실을 다른 층에서 확인해야 합니다. 계약 테스트나 소수의 실통신 테스트가 그 역할을 합니다.
5-3. 목 대신 쓸 수 있는 대역들
목 하나로 뭉뚱그리면 논쟁이 길어집니다. 대역에도 종류가 있고 유지 비용이 다릅니다.
- 스텁: 정해진 값을 돌려주기만 합니다. 호출 방식을 검증하지 않으므로 구현 변경에 강합니다. 대부분의 경우 이것으로 충분합니다.
- 가짜 구현: 실제와 같은 계약을 만족하는 가벼운 구현입니다. 인메모리 저장소가 대표적입니다. 만드는 비용은 크지만 여러 테스트가 공유하므로 총비용은 낮아지고, 계약이 코드로 남아 검증도 가능합니다.
- 엄격한 목: 호출 횟수와 인자를 단언합니다. "부작용이 실제로 일어났는가"가 검증 대상일 때만 씁니다. 그 외에는 구현에 붙어 리팩터링을 막는 쪽으로 작동합니다.
- 실제 컨테이너: 저장소나 메시지 브로커처럼 컨테이너로 수십 초 안에 뜨는 것은 그냥 실제를 쓰는 편이 정확합니다.
"목을 쓸 것인가"보다 "어떤 대역이 이 검증에 필요한 최소인가"가 더 좋은 질문입니다. 대부분의 논쟁은 엄격한 목을 기본값으로 쓰는 습관에서 시작됩니다.
6. 커버리지 숫자를 다루는 법
커버리지 목표치 역시 합의가 없는 영역입니다. 논쟁의 축을 먼저 정리합니다.
- 목표치를 두자는 쪽: 숫자가 없으면 테스트가 없는 영역이 조용히 자랍니다. 하한선은 최소한의 규율을 만듭니다.
- 목표치가 해롭다는 쪽: 커버리지는 실행된 줄을 셀 뿐 검증된 동작을 세지 않습니다. 목표를 지표로 삼는 순간 단언 없는 테스트, 게터 테스트, 생성된 코드 포함 같은 우회로가 등장합니다.
양쪽 다 맞습니다. 그래서 숫자를 쓰되 쓰는 방식을 바꾸는 것이 현실적입니다.
- 절대값 목표 대신 변화량 관문: 전체 80% 같은 목표 대신 "이번 변경에서 새로 추가된 줄의 커버리지"를 봅니다. 기존 부채를 건드리지 않으면서 새 코드의 품질만 관리할 수 있고, 우회 동기도 훨씬 약합니다.
- 낮은 쪽을 조사 신호로만 사용: 커버리지가 낮은 모듈은 벌점 대상이 아니라 조사 대상입니다. 테스트가 어려운 구조라는 신호일 때가 많습니다.
- 높은 숫자를 신뢰의 근거로 쓰지 않기: 90%는 "10%가 위험하다"가 아니라 "90%가 실행되었다"는 뜻일 뿐입니다. 실행과 검증은 다릅니다.
- 분기 커버리지를 함께 보기: 줄 커버리지만 보면 조건문의 한쪽만 검증한 상태가 가려집니다.
커버리지가 답하지 못하는 질문은 "이 테스트가 실제로 결함을 잡아내는가"입니다. 이걸 직접 재는 방법도 있습니다. 코드에 작은 변형을 자동으로 심고 테스트가 그 변형을 잡아내는지 보는 방식입니다. 잡아내지 못한 변형이 많다면 실행은 되지만 검증은 없는 코드가 많다는 뜻입니다. 실행 비용이 커서 전체 스위트에 상시 적용하기는 어렵지만, 핵심 도메인 모듈에 한 번만 돌려도 단언이 비어 있는 구역이 드러납니다.
7. 결함이 실제로 어디서 나오는가 — 데이터로 비율 정하기
7-1. 4주면 충분한 기록
비율을 정하는 가장 확실한 방법은 논쟁이 아니라 기록입니다. 프로덕션 결함과 롤백에 다음 두 필드만 붙이면 4주 안에 방향이 보입니다.
- 어느 층이 잡았어야 했는가: 단위 / 통합 / 계약 / E2E / 어느 층도 아님(설계·요구사항 문제).
- 왜 못 잡았는가: 테스트가 없었다 / 있었지만 목이 실제와 달랐다 / 환경 차이 / 데이터 차이 / 동시성 및 타이밍.
두 필드의 분포가 곧 투자처입니다. "통합 층이 잡았어야 했다"가 절반이면 통합을 늘리는 것이 맞고, "목이 실제와 달랐다"가 절반이면 목을 줄이거나 계약 테스트를 넣는 것이 맞습니다. 어느 층도 아님이 절반이면 테스트를 늘려도 결함은 줄지 않습니다. 그때는 요구사항 정의와 설계 리뷰 쪽으로 투자를 옮겨야 합니다.
7-2. 배포 지표와 함께 보기
DORA는 네 가지 지표를 정의합니다. 배포 빈도, 변경 리드 타임(변경이 "committed to version control"에서 "deployed in production"까지 걸리는 시간), 변경 실패율("The ratio of deployments that require immediate intervention following a deployment"), 실패 배포 복구 시간입니다.
테스트 전략의 효과는 변경 실패율과 리드 타임의 조합으로 확인합니다. 테스트를 늘렸는데 변경 실패율이 그대로면 잘못된 층에 투자한 것이고, 변경 실패율은 내려갔는데 리드 타임이 크게 늘었다면 비용이 수익을 넘어선 것입니다. DORA는 이 둘이 상충 관계가 아니라고 말합니다. "DORA's research has repeatedly demonstrated that speed and stability are not tradeoffs." 둘 중 하나가 나빠졌다면 절충이 아니라 설계 문제로 보는 편이 맞습니다.
7-3. 프로덕션도 검증 층이다
모든 결함을 배포 전에 잡을 수는 없습니다. Google SRE 워크북은 카나리를 "a partial and time-limited deployment of a change in a service and its evaluation"으로 정의하고 나머지 집단을 대조군으로 둡니다. 여기서 나온 계산이 유용합니다. 5% 카나리 집단에서 오류율이 20%라도 전체 오류율은 1%에 그칩니다. 즉 카나리는 "결함을 사용자에게 노출하지 않는 장치"가 아니라 노출 규모를 오류 예산 안에 가두는 장치입니다.
같은 문서는 시간에 따른 변화가 관측값의 큰 교란 요인이므로 배포 전후 비교 대신 동시에 도는 대조군과 비교하라고 권합니다. 테스트 층과 배포 층은 대체재가 아니라 보완재이며, E2E를 늘릴지 카나리를 정교하게 만들지는 같은 예산을 두고 겨루는 선택입니다. 오류 예산 관점의 정리는 SLI/SLO/Error Budget에 있습니다.
8. 느리고 깨지는 테스트를 관리하는 운영 규칙
전략 문서보다 실제 품질을 좌우하는 것은 이 운영 규칙들입니다.
- 플래키 테스트에 소유자와 기한을 붙인다: 재실행으로 넘기는 순간 스위트 전체의 신뢰가 깎입니다. 실패율을 자동 수집해 임계치를 넘으면 이슈를 자동 생성하고, 담당자와 기한 없이는 격리하지 않는 규칙이 필요합니다.
- 격리는 삭제 예약이다: 격리한 테스트에 만료일을 붙입니다. 기한까지 고쳐지지 않으면 지웁니다. 영원히 꺼져 있는 테스트는 커버리지 숫자만 부풀리고 아무것도 지키지 않습니다.
- 단계별 시간 예산을 정한다: "커밋 단계 5분, 병합 전 15분" 같은 상한을 두고, 초과하면 테스트를 추가하기 전에 무엇을 뺄지 먼저 정합니다. 예산이 없으면 파이프라인은 단조 증가합니다.
- 가장 느린 20개를 주기적으로 본다: 실행 시간 상위 목록은 대개 몇 개가 전체를 지배합니다. 상위 항목만 손봐도 총시간이 크게 줄어드는 경우가 많습니다.
- 실패 메시지를 자산으로 취급한다: "expected true but got false"만 남기는 테스트는 실패해도 진단에 도움이 되지 않습니다. 실패 시 어떤 입력과 어떤 상태였는지 남기게 하면 디버깅 시간이 줄어듭니다.
- 테스트도 리뷰 대상이다: 테스트 코드는 리뷰에서 가장 자주 건너뛰는 부분이지만, 유지 비용의 대부분이 여기서 발생합니다.
- 공유 상태를 테스트 사이에 남기지 않는다: 순서에 의존하는 테스트는 병렬 실행을 켜는 순간 무작위로 깨집니다. 실행 순서를 무작위로 섞는 옵션을 켜 두면 이 의존을 조기에 드러낼 수 있습니다.
- 시간과 난수를 주입한다: 현재 시각과 난수를 코드가 직접 읽으면 그 테스트는 언젠가 반드시 깨집니다. 월말, 윤년, 시간대 전환에서 터지는 실패는 대부분 여기서 시작됩니다.
Fowler가 E2E를 "notoriously flaky"라고 표현한 것은 도구 문제가 아니라 그 층의 구조적 성질입니다. E2E 층은 늘리는 것이 아니라 좁게 유지하고 강하게 관리하는 층이라고 보는 편이 현실적입니다.
9. 팀의 테스트 전략을 한 장으로 쓰기
전략은 문서가 아니라 합의입니다. 한 장을 넘기면 아무도 읽지 않습니다.
1. 우리가 테스트로 사려는 것
(회귀 방어 / 설계 피드백 / 배포 신뢰 중 우선순위)
2. 층의 정의와 실행 위치
빠른 층: 3초 이내, 외부 의존 없음 → 커밋마다
통합 층: 컨테이너 필요, 30초 이내 → 병합 전
E2E 층: 스테이징 필요, 10분 이내 → 배포 파이프라인
3. 목을 쓰는 곳과 쓰지 않는 곳
대체: 외부 결제·메일, 시각, 난수
실제: 자체 저장소(컨테이너), 내부 모듈
4. 커버리지 규칙
변경분 커버리지 하한만 관문으로 사용, 전체 목표치 없음
5. 시간 예산
커밋 5분 / 병합 전 15분, 초과 시 추가 전에 제거 먼저
6. 플래키 대응
실패율 임계 초과 시 자동 이슈, 격리에는 담당자와 만료일 필수
7. 재검토 주기
분기 1회, 결함 분포 데이터로 2·3항을 갱신
이 문서의 값어치는 내용보다 갱신 이력에 있습니다. 결함 분포가 바뀌면 비율도 바뀌어야 하고, 그 변경 근거가 남아 있어야 다음 사람이 이유를 이해합니다. 리뷰 관점의 운영 규칙은 소프트웨어 테스팅 전략 완전 가이드와 함께 보면 좋습니다.
퀴즈: 실력을 확인해 보세요
퀴즈 1: 옆 팀이 "우리는 트로피를 쓴다"며 통합 비중을 70%로 옮겼습니다. 그대로 따라가도 되는가?
정답: 통합 환경 구성 비용, E2E 오탐률, 단위의 정의, 테스트의 주 목적 네 가지 값을 우리 팀 기준으로 확인한 뒤에 결정합니다.
설명: 트로피와 피라미드는 서로 다른 축 값을 가진 코드베이스를 전제한 모델입니다. 통합 환경이 컨테이너로 30초에 뜨는 팀과 전용 스테이징이 필요한 팀의 최적 비율은 다를 수밖에 없습니다. Dodds 본인도 트로피가 개별 코드베이스를 대상으로 한 모델이며 마이크로서비스 구성에 그대로 적용되지 않을 수 있다고 밝힙니다. 비율은 복사하는 것이 아니라 축 값에서 도출하는 것입니다.
퀴즈 2: 커버리지가 60%에서 85%로 올랐는데 프로덕션 결함은 그대로입니다. 무엇을 확인하나?
정답: 결함이 어느 층에서 잡혔어야 했는지 분포를 먼저 확인합니다.
설명: 커버리지는 실행된 줄을 셀 뿐 검증된 동작을 세지 않습니다. 단언이 약한 테스트가 늘면 숫자는 오르고 결함은 그대로입니다. 더 흔한 경우는 잘못된 층에 투자한 것입니다. 결함의 절반이 통합 지점에서 나오는데 단위 테스트만 늘리면 숫자만 오릅니다. "어느 층이 잡았어야 했는가"와 "왜 못 잡았는가" 두 필드를 4주만 기록해도 방향이 보입니다.
퀴즈 3: 결제 게이트웨이 연동에서 목을 쓸지 실제를 쓸지 정해야 합니다. 판단 기준은?
정답: 외부 시스템이므로 목으로 대체하되, 직접 목을 만들지 말고 얇은 어댑터를 만들어 그 어댑터를 대역으로 바꿉니다. 그리고 목이 실제와 같다는 사실을 별도 층에서 확인합니다.
설명: 외부 시스템은 통제할 수 없고 느리고 비용이 발생하므로 대부분의 테스트에서 대역이 필요합니다. 문제는 내가 소유하지 않은 인터페이스를 상상해 목으로 굳히면 그 상상이 틀렸을 때 아무 테스트도 실패하지 않는다는 점입니다. 어댑터로 경계를 소유한 뒤 대역화하고, 계약 테스트나 소수의 실통신 테스트로 어댑터의 가정을 정기 검증하는 구성이 안전합니다.
퀴즈 4: E2E 스위트가 하루 3회 무작위로 실패합니다. 재실행 자동화를 넣어야 하는가?
정답: 아닙니다. 재실행은 신호를 지우는 조치이고, 실패율을 지표로 수집해 소유자와 기한이 붙은 격리·수정 절차로 보내야 합니다.
설명: 재실행 자동화는 오탐 비용을 겉으로만 낮추고 실제로는 스위트 전체의 신뢰를 깎습니다. 빨간 파이프라인을 습관적으로 다시 돌리기 시작하면 진짜 결함이 섞여 들어와도 구분되지 않습니다. E2E는 구조적으로 오탐이 잦은 층이므로 층을 좁게 유지하고, 아래층이 원리적으로 볼 수 없는 것만 남기는 편이 낫습니다. 격리에는 반드시 만료일을 붙여 영원히 꺼져 있는 테스트가 생기지 않게 합니다.
퀴즈 5: 테스트를 대거 추가한 뒤 변경 실패율은 절반이 됐지만 리드 타임이 2배가 됐습니다. 어떻게 판단하나?
정답: 절충이 아니라 설계 문제로 보고, 어느 테스트가 시간을 지배하는지와 같은 결함을 두 층에서 중복으로 잡고 있는지를 먼저 확인합니다.
설명: DORA는 속도와 안정성이 상충 관계가 아니라고 반복해서 밝혔습니다. 둘 중 하나가 크게 나빠졌다면 대개 파이프라인 구성 문제입니다. 실행 시간 상위 목록은 소수의 테스트가 지배하는 경우가 많고, E2E가 검증하는 분기를 단위 테스트가 이미 전부 덮고 있으면 그 E2E는 비용만 남습니다. 단계별 시간 예산을 정하고 추가 전에 제거를 먼저 결정하는 규칙이 필요합니다.
마치며
피라미드와 트로피 중 무엇이 맞느냐는 질문에는 답이 없습니다. 두 모델의 원저자가 모두 그렇게 말하고 있고, 특히 Dodds가 인용한 문장은 직설적입니다. 비율 논쟁 자체가 주의를 분산시킨다는 것입니다.
대신 답이 있는 질문은 이렇습니다. 우리 팀에서 단위의 경계는 어디인가. 통합 환경을 띄우는 데 얼마가 드는가. E2E의 오탐률은 몇 퍼센트인가. 지난 분기 결함은 어느 층이 잡았어야 했는가. 이 네 질문에 숫자로 답하면 비율은 결론이 아니라 계산 결과로 나옵니다.
그리고 그 계산은 한 번으로 끝나지 않습니다. 스택이 바뀌고, 도구가 빨라지고, 결함 분포가 이동하면 비율도 따라 움직여야 합니다. 테스트 전략의 품질은 비율의 정확도가 아니라 갱신 주기에 달려 있습니다.
참고 자료
- The Practical Test Pyramid — Martin Fowler — 피라미드의 핵심 문장, 모델의 한계에 대한 자기 지적, 단위의 정의가 사람마다 다르다는 지적, E2E의 오탐과 유지 비용 평가를 인용했습니다. 2026-08-15 확인.
- The Testing Trophy and Testing Classifications — Kent C. Dodds — 네 층 구성과 통합 층 중심 주장, 사용 방식을 닮을수록 신뢰가 커진다는 문장, 단위 정의 통일이 무의미하다는 지적, Justin Searls 인용, 개별 코드베이스 전제를 인용했습니다. 2026-08-15 확인.
- DORA — Four keys metrics — 변경 리드 타임과 변경 실패율의 정의, 속도와 안정성이 상충 관계가 아니라는 연구 결과를 인용했습니다. 2026-08-15 확인.
- Google SRE Workbook — Canarying Releases — 카나리의 정의, 5% 집단에서 20% 오류율이 전체 1%가 된다는 계산, 시간에 따른 교란 때문에 전후 비교 대신 동시 대조군을 쓰라는 권고를 인용했습니다. 2026-08-15 확인.
- 네 가지 갈림 축, 비용 함수 네 항목, 결함 기록 두 필드, 한 장짜리 전략 템플릿은 위 자료에 나오는 것이 아니라 이 글에서 정리한 절차입니다.
이어서 읽기
- 이 블로그의 관련 글: 소프트웨어 테스팅 전략 완전 가이드
- 이 블로그의 관련 글: 속성 기반 테스트 실전
- 이 블로그의 관련 글: 검증 설계 — 테스트를 신뢰 근거로 보기
- 이 블로그의 관련 글: SLI/SLO/Error Budget 기반 신뢰성 엔지니어링
- 관련 도구: SLO 오류 예산 계산기
완전 가이드 시리즈
The Complete Guide to Test Strategy: Decision Criteria Instead of the Pyramid Debate
- Introduction
- 1. Why Tests Exist: Regression Defense or Design Pressure
- 2. Pyramid and Trophy — the Real Axes of the Argument
- 3. Nobody Ever Agreed What "Unit" Means
- 4. Seeing It as a Cost Function: Writing, Running, Maintaining, False Alarms
- 5. What to Replace With a Mock
- 6. What to Do With Coverage Numbers
- 7. Where Defects Actually Come From — Setting the Ratio From Data
- 8. Operational Rules for Slow and Flaky Tests
- 9. Writing Your Team's Test Strategy on One Page
- Quiz: Check Your Understanding
- Closing
- References
- Further reading
Introduction
This blog already carries several Korean complete guides on testing. The Complete Guide to Software Testing Strategies covers what kinds of tests exist and how to use them, Property-Based Testing in Practice covers one specific technique, and Designing Verification — Tests as Grounds for Trust treats verification as an individual skill. All three are about which tests exist and how to write them.
This post is different. It is about how a team decides its own ratio. It does not pick between the pyramid and the trophy; it decomposes the argument into the axes it actually turns on. Once you know the axes you can answer "which side is our stack on?" yourself, and that lasts longer than copying somebody else's conclusion.
The conclusion up front: the ratio is not an answer, it is a result. Measure where defects come from, how expensive it is to stand up an integration environment, and how often your E2E layer cries wolf, and the ratio derives itself.
1. Why Tests Exist: Regression Defense or Design Pressure
When a testing argument goes in circles, it is usually because the participants assume different purposes. Tests exist for roughly four reasons, and a different purpose implies a different shape of good test.
- Regression defense: tells you whether something that worked yesterday broke today. For this purpose a test must attach to externally observable behavior, not to the implementation. A test you have to edit alongside every refactoring is not a regression asset, it is a liability.
- Design pressure: code that is hard to test is usually tightly coupled code. Writing the test first shapes the interface from the caller's point of view. Here fine-grained unit tests help, and some coupling to the implementation is accepted as the price.
- Specification and documentation: records what the code guarantees in executable form. Being read is the point, so naming and placement matter.
- Release confidence: makes it possible to ship without a human sign-off. Integration, contract and E2E carry more weight here.
The first conflict is already visible. Prioritizing regression defense pulls tests away from the implementation; prioritizing design pressure pushes them toward it. When the two purposes collide in one codebase, a single question resolves it: "is it acceptable for this test to be edited during a refactoring?" For a regression test, no. For a design-pressure test, yes. Naming that distinction in a team halves the amount of arguing in review.
2. Pyramid and Trophy — the Real Axes of the Argument
2-1. What the two sources actually say
The test pyramid is most often cited via Martin Fowler. The core is one sentence: "Write lots of small and fast unit tests. Write some more coarse-grained tests and very few high-level tests that test your application from end to end." The higher you go, the slower and more expensive, so keep the count down.
What deserves attention is that the same article admits the limits of its own model. It says "the concept of the test pyramid falls a little short if you take a closer look," notes of the middle layer that "service test is a term that is hard to grasp," and adds that in modern front-end setups "UI tests don't have to be on the highest level." Of the top layer it says E2E tests are "notoriously flaky and often fail for unexpected and unforeseeable reasons" and "require a lot of maintenance and run pretty slowly."
The testing trophy is Kent C. Dodds' formulation. It splits into static analysis, unit, integration and E2E, and makes integration the largest section. The justification is confidence per unit of investment, in his words: "The more your tests resemble the way your software is used, the more confidence they can give you."
2-2. Both camps say the ratio debate is not the point
Crucially, the trophy side does not treat this as a search for the right answer either. In the same article Dodds notes that definitions of unit test diverge to the tune of "24 different definitions of unit test," and writes "Any attempt to come to a single definition for all these terms is a futile endeavor." He then quotes Justin Searls: "People love debating what percentage of which type of tests to write, but it's a distraction."
Fowler says something equivalent: "If you ask three different people what 'unit' means in the context of unit tests, you'll probably receive four different, slightly nuanced answers."
So the flagship text of each camp says the ratio debate is not the substance. The reason the argument nevertheless never ends in practice is that several different conditions hide behind that one number.
2-3. The four axes it actually turns on
Axis 1 Where you draw the boundary of a "unit"
one function = a unit → unit count explodes, looks like a pyramid
one module = a unit → the same tests get filed as "integration",
looks like a trophy
Axis 2 What an integration environment costs to stand up
30 seconds in a container → raising the integration share is rational
a dedicated staging env → raising it destroys the feedback loop
Axis 3 The false-alarm rate of your E2E layer
under 1% → E2E can be used as grounds for confidence
over 10% → noise, not signal; shrink the layer itself
Axis 4 What the tests are primarily for
regression defense → behavior-centric, larger units, few mocks
design feedback → close to the implementation, small units,
mocks allowed
Plug your team's real values into the four axes and the ratio falls out without an argument. If the container-based integration environment comes up in 30 seconds, the E2E false-alarm rate is 8%, and the team's main purpose is regression defense, the result looks like a trophy. If domain logic is thick and the integration environment is expensive, it looks like a pyramid. Neither model is wrong; the two articles simply assumed codebases with different axis values.
2-4. What to check before you copy a model
Dodds himself notes that the trophy targets an individual codebase and may not transfer directly to microservice or serverless setups. Whichever model you adopt, carry the context its author assumed along with it. Copying only the picture means setting your ratio from somebody else's axis values.
3. Nobody Ever Agreed What "Unit" Means
3-1. The spectrum of definitions
Half the ratio argument is a vocabulary problem. The same test gets filed as a unit test by one team and an integration test by another.
narrow ←──────────────────────────────────────────────→ wide
one function one class one module one process
all collaborators some mocked internals real only external doubled
mocked DB in-memory DB in a container
As both Fowler and Dodds point out, there is no standard boundary anywhere on this spectrum. So a goal of "70% unit tests" means nothing until the boundary is fixed. Moving the boundary alone swings the number for the same codebase from 20% to 80%.
3-2. Name the properties, not the category
The workaround that holds up in practice is to abandon the category names and describe the properties instead.
- Isolation level: how much real code does this test actually execute.
- Runtime: milliseconds, seconds, or minutes.
- Environment requirement: does it finish in-process, need another process, or need the network.
- Determinism: does the same input always produce the same result.
Once those four values are fixed, the label is unnecessary. Structure your CI stages by those values rather than by names. "Finishes in three seconds with no external dependency" runs on every commit, "needs a container" runs before merge, and "needs staging" runs in the deployment pipeline.
4. Seeing It as a Cost Function: Writing, Running, Maintaining, False Alarms
Treating a test not as an asset but as a contract with both a cost and a return makes decisions easier. The cost has four parts.
- Writing cost: paid once. The most visible part, and usually the smallest share of total cost.
- Running cost: paid on every commit. In a pipeline that runs 50 times a day, one test getting two seconds slower burns hours per year. The bigger loss is developers losing context while they wait.
- Maintenance cost: paid every time the code changes. A test bolted to implementation detail sends an invoice with every refactoring. This item frequently dominates the total.
- False-alarm cost: the time spent deciding whether a failure is real, plus the cost of lost trust. That last part is the most expensive. Once people habitually re-run a red pipeline, that suite has stopped being an asset.
There is a single return item: the expected value of defects prevented — the probability of a defect that would have reached production, times its cost.
Put numbers on it. Say an E2E test takes four hours to write, 40 seconds to run, and two hours of fixing twice a quarter. In a pipeline that runs 30 times a day, execution alone burns about 68 hours a year, and with maintenance the annual total passes 80 hours. Five or six unit tests covering the same logic often total less than half that. Compare only writing cost and E2E looks cheap; compare total cost and it usually is not.
That yields a practical rule. If two layers catch the same defect, delete the expensive one. If unit tests already cover every logic branch an E2E exercises, that E2E is pure cost. Leave in E2E only what lower layers cannot see in principle: wiring between layers such as routing, authentication, serialization and configuration.
The opposite mistake is just as common. Forcing a check down into a unit test when it cannot live there means that the moment a mock diverges from reality, everything is green and production is broken. The criterion is "is this defect visible at this layer in principle," not "is this layer cheap."
5. What to Replace With a Mock
How far to take mocking is one of the oldest arguments in this field, and it is still unsettled.
5-1. The two positions
- The broad-mocking side: replacing collaborators with doubles makes tests fast and narrows the cause of failure. The name of the failing test alone tells you which unit broke. You can also design against a collaborator that does not exist yet.
- The narrow-mocking side: a mock verifies your assumption, not the other side's actual behavior. When the other side changes, the mock stays quiet, which manufactures the all-green-but-production-broken situation. Mocks that assert call order and arguments also bolt themselves to the implementation and block refactoring.
5-2. The axis, and a working rule
The axis of the argument is whether the thing being replaced is under your control. The following rule generally works well.
Replace with a double Use the real thing
───────────────────────── ─────────────────────────
external systems (payments, collaborators inside the same codebase
mail) pure computation and value objects
non-determinism (clock, stores you can start in a container
random) stores replaceable by an in-memory version
failures hard to reproduce
slow I/O (seconds or more)
Two further rules matter. First, do not hand-write mocks for interfaces you do not own. Wrap the external SDK in a thin adapter and double the adapter instead. Freezing an imagined version of an external SDK into a mock means nothing fails when that imagination turns out wrong. Second, if you mocked something, confirm at another layer that the mock matches reality. Contract tests or a small number of real-communication tests play that role.
5-3. The doubles you can reach for instead
Lumping everything under "mock" makes the argument longer than it needs to be. Doubles come in kinds with different maintenance costs.
- Stub: returns a fixed value and nothing more. It does not verify how it was called, so it is robust to implementation change. In most cases this is enough.
- Fake: a lightweight implementation satisfying the same contract as the real thing — an in-memory store is the classic example. Expensive to build, but shared across many tests, so total cost drops, and the contract survives as code you can verify.
- Strict mock: asserts call counts and arguments. Use it only when "did the side effect actually happen" is the thing under test. Otherwise it attaches to the implementation and works against refactoring.
- A real container: for a store or a message broker that starts in tens of seconds, using the real thing is simply more accurate.
"Which double is the minimum this check needs?" is a better question than "should we mock?" Most of the argument starts from the habit of reaching for a strict mock by default.
6. What to Do With Coverage Numbers
Coverage targets are another area without consensus. Lay out the axes first.
- The pro-target side: without a number, untested regions grow quietly. A floor creates a minimum of discipline.
- The anti-target side: coverage counts lines executed, not behavior verified. The moment the target becomes a metric, workarounds appear — tests with no assertions, getter tests, generated code counted in.
Both are right. The realistic move is to keep the number but change how you use it.
- A delta gate instead of an absolute target: rather than "80% overall," look at the coverage of lines newly added by this change. It manages the quality of new code without touching legacy debt, and the incentive to game it is far weaker.
- Use low numbers only as an investigation signal: a low-coverage module is a subject for inquiry, not a penalty. It is often a signal that the structure is hard to test.
- Do not use a high number as grounds for confidence: 90% does not mean "10% is risky," it means "90% was executed." Execution and verification are different things.
- Look at branch coverage too: line coverage alone hides a conditional where only one side was exercised.
The question coverage cannot answer is "does this test actually catch defects?" There is a way to measure that directly: automatically plant small mutations in the code and see whether the tests catch them. A large number of surviving mutations means a lot of code is executed but not verified. It is expensive to run continuously across a whole suite, but running it once over a core domain module exposes the regions where assertions are missing.
7. Where Defects Actually Come From — Setting the Ratio From Data
7-1. Four weeks of records is enough
The most reliable way to set a ratio is a record, not an argument. Attach just two fields to production defects and rollbacks and the direction becomes visible within four weeks.
- Which layer should have caught it: unit / integration / contract / E2E / none of them (a design or requirements problem).
- Why it was missed: no test existed / a test existed but the mock differed from reality / environment difference / data difference / concurrency and timing.
The distribution across those two fields is your investment plan. If half say "integration should have caught it," raising integration is correct. If half say "the mock differed from reality," reducing mocks or adding contract tests is correct. If half say none of them, adding tests will not reduce defects. That is when investment should move toward requirements definition and design review.
7-2. Read it alongside delivery metrics
DORA defines four metrics: deployment frequency, change lead time (the time for a change to go from "committed to version control" to "deployed in production"), change fail rate ("The ratio of deployments that require immediate intervention following a deployment"), and failed deployment recovery time.
Judge a test strategy by the combination of change fail rate and lead time. If you added tests and the change fail rate did not move, you invested in the wrong layer; if the fail rate dropped but lead time ballooned, cost exceeded return. DORA says these two are not a trade-off: "DORA's research has repeatedly demonstrated that speed and stability are not tradeoffs." If one of them got worse, treat it as a design problem rather than an unavoidable compromise.
7-3. Production is a verification layer too
You cannot catch every defect before release. The Google SRE Workbook defines canarying as "a partial and time-limited deployment of a change in a service and its evaluation," with the remaining fleet as the control. One calculation from it is especially useful: a 5% canary population with a 20% error rate yields only a 1% overall error rate. A canary is therefore not a device for hiding defects from users but a device for keeping the exposure inside the error budget.
The same document warns that change over time is one of the largest confounders in observed metrics, so compare against a concurrently running control rather than a before-and-after snapshot. Test layers and release layers are complements, not substitutes, and "add more E2E" versus "make the canary sharper" are competing claims on the same budget. The error-budget framing is covered in SLI/SLO/Error Budget.
8. Operational Rules for Slow and Flaky Tests
These operational rules affect real quality more than any strategy document.
- Give every flaky test an owner and a deadline: the moment you wave one through with a re-run, trust in the whole suite drops. Collect failure rates automatically, open an issue when a threshold is crossed, and forbid quarantining without an assignee and a date.
- Quarantine is scheduled deletion: attach an expiry date to every quarantined test. If it is not fixed by then, delete it. A test that is permanently disabled inflates coverage numbers and protects nothing.
- Set a time budget per stage: put a ceiling such as "5 minutes at commit, 15 minutes before merge," and when it is exceeded, decide what to remove before adding anything. Without a budget a pipeline only grows.
- Review the slowest 20 regularly: the top of the runtime list is usually dominated by a handful of tests. Fixing only those often cuts total time substantially.
- Treat failure messages as an asset: a test that leaves only "expected true but got false" does not help when it fails. Recording the input and state at failure time cuts debugging time.
- Test code is review material too: it is the part most often skipped in review, yet most of the maintenance cost originates there.
- Leave no shared state between tests: order-dependent tests break randomly the moment parallel execution is enabled. Turning on randomized test ordering surfaces that dependency early.
- Inject time and randomness: if code reads the current clock or a random source directly, that test will eventually break. Failures that erupt at month end, on a leap day, or at a time-zone transition mostly start here.
Fowler calling E2E "notoriously flaky" describes a structural property of that layer, not a tooling problem. Treat E2E as a layer to keep narrow and manage tightly, not one to grow.
9. Writing Your Team's Test Strategy on One Page
A strategy is an agreement, not a document. Past one page, nobody reads it.
1. What we are buying with tests
(priority among regression defense / design feedback / release confidence)
2. Layer definitions and where they run
fast layer: under 3s, no external dependency → every commit
integration layer: needs a container, under 30s → before merge
E2E layer: needs staging, under 10 minutes → deploy pipeline
3. Where we mock and where we do not
doubled: external payments and mail, clock, randomness
real: our own stores (container), internal modules
4. Coverage rule
only a delta-coverage floor is a gate; no overall target
5. Time budget
5 min at commit / 15 min before merge; over budget means remove before adding
6. Flaky handling
automatic issue above a failure-rate threshold;
quarantine requires an assignee and an expiry date
7. Review cadence
once a quarter; update items 2 and 3 from defect distribution data
The value of this document is less in its content than in its revision history. When the defect distribution shifts, the ratio must shift too, and the reasoning has to be on record for the next person to understand it. Read it alongside The Complete Guide to Software Testing Strategies.
Quiz: Check Your Understanding
Quiz 1: A neighbouring team moved integration to 70%, saying "we use the trophy." Should you follow?
Answer: Not until you check four values for your own team: integration environment cost, E2E false-alarm rate, the definition of a unit, and the primary purpose of your tests.
Explanation: The trophy and the pyramid each assume a codebase with particular axis values. A team whose integration environment comes up in a container in 30 seconds and a team that needs dedicated staging cannot have the same optimum. Dodds himself notes the trophy targets an individual codebase and may not transfer to a microservice setup. A ratio is derived from axis values, not copied.
Quiz 2: Coverage went from 60% to 85% and production defects did not move. What do you check?
Answer: The distribution of which layer should have caught each defect, first.
Explanation: Coverage counts lines executed, not behavior verified. Add tests with weak assertions and the number climbs while defects stay. The more common case is investing in the wrong layer: if half your defects arise at integration points and you only add unit tests, only the number moves. Recording "which layer should have caught it" and "why it was missed" for four weeks is enough to see the direction.
Quiz 3: You must decide whether to mock or use the real payment gateway integration. What is the criterion?
Answer: It is an external system, so use a double — but do not hand-write the mock. Wrap it in a thin adapter and double the adapter, then confirm at a separate layer that the double matches reality.
Explanation: External systems are uncontrollable, slow and billable, so most tests need a double. The problem is that freezing an imagined version of an interface you do not own means nothing fails when that imagination is wrong. Own the boundary with an adapter, double that, and periodically validate the adapter's assumptions with contract tests or a handful of real-communication tests.
Quiz 4: The E2E suite fails randomly about three times a day. Should you add automatic re-runs?
Answer: No. A re-run erases the signal. Collect the failure rate as a metric and route it into a quarantine-and-fix procedure with an owner and a deadline.
Explanation: Automatic re-runs lower the false-alarm cost only on the surface while eroding trust in the whole suite. Once people habitually re-run a red pipeline, a real defect mixed in becomes indistinguishable. E2E is structurally prone to false alarms, so keep the layer narrow and leave in it only what lower layers cannot see in principle. Always attach an expiry date to a quarantine so permanently disabled tests do not accumulate.
Quiz 5: After adding many tests, change fail rate halved but lead time doubled. How do you read that?
Answer: Treat it as a design problem rather than an inevitable trade-off, and first check which tests dominate the runtime and whether two layers are catching the same defect.
Explanation: DORA has repeatedly reported that speed and stability are not trade-offs. If one of them degrades badly, it is usually a pipeline composition problem. The runtime top-list is typically dominated by a handful of tests, and an E2E whose branches are already fully covered by unit tests is pure cost. Set a per-stage time budget and make removal a precondition for addition.
Closing
There is no answer to "is the pyramid or the trophy correct." The authors of both models say so themselves, and the line Dodds quotes is blunt: the percentage debate is a distraction.
The questions that do have answers are these. Where is the unit boundary in our team? What does standing up an integration environment cost? What is our E2E false-alarm rate? Which layer should have caught last quarter's defects? Answer those four with numbers and the ratio comes out as a calculation, not a conclusion.
And that calculation is never done once. When the stack changes, the tooling gets faster, or the defect distribution moves, the ratio must move with it. The quality of a test strategy depends on its revision cadence, not on the precision of its ratio.
References
- The Practical Test Pyramid — Martin Fowler — quoted for the core pyramid sentence, the article's own acknowledgement of the model's limits, the observation that people disagree on what a unit is, and its assessment of E2E flakiness and maintenance cost. Checked 2026-08-15.
- The Testing Trophy and Testing Classifications — Kent C. Dodds — quoted for the four-layer structure and integration-heavy argument, the confidence-through-resemblance line, the futility of a single unit definition, the Justin Searls quote, and the individual-codebase premise. Checked 2026-08-15.
- DORA — Four keys metrics — quoted for the definitions of change lead time and change fail rate, and the finding that speed and stability are not trade-offs. Checked 2026-08-15.
- Google SRE Workbook — Canarying Releases — quoted for the definition of canarying, the calculation that a 5% population with a 20% error rate yields 1% overall, and the recommendation to use a concurrent control rather than a before-and-after comparison. Checked 2026-08-15.
- The four axes, the four-part cost function, the two defect-record fields, and the one-page strategy template do not appear in the sources above — they are procedures assembled in this post.
Further reading
- Related post on this blog: The Complete Guide to Software Testing Strategies
- Related post on this blog: Property-Based Testing in Practice
- Related post on this blog: Designing Verification — Tests as Grounds for Trust
- Related post on this blog: Reliability Engineering with SLI/SLO/Error Budget
- Related tool: SLO Error Budget Calculator
Complete Guide Series