Split View: 검증 설계 — 테스트를 통과 여부가 아니라 신뢰 근거로 보기
검증 설계 — 테스트를 통과 여부가 아니라 신뢰 근거로 보기
- 초록불은 그 자체로는 아무것도 뜻하지 않습니다
- 존재는 보일 수 있고 부재는 보일 수 없습니다
- 각 테스트에 신뢰 문장을 붙이기
- 커버리지가 재는 것과 재지 못하는 것
- 검증하지 않기로 한 것을 적기
- 판정 기준이 없는 영역
- 직접 해보기
- 이어서 읽기
- 참고 자료
초록불은 그 자체로는 아무것도 뜻하지 않습니다
CI가 초록입니다. 그래서 무엇을 알게 된 걸까요.
이 질문에 답하지 못하는 초록불이 많습니다. 통과했다는 사실만 있고, 통과가 무엇을 보장하는지는 아무도 문장으로 갖고 있지 않습니다. 이 상태에서 테스트는 배포 절차의 관문이지 판단의 근거가 아닙니다. 관문은 통과하면 잊히지만, 근거는 사고가 났을 때 다시 꺼내집니다.
차이는 배포 직후에 드러납니다. 장애가 나면 누군가 반드시 묻습니다. 이거 테스트는 안 잡았나요. 이때 "그 영역은 원래 검증 범위 밖이었습니다"라고 답할 수 있으면 조직은 다음 결정을 할 수 있고, "잡았어야 하는데요"밖에 못 하면 같은 일이 반복됩니다.
존재는 보일 수 있고 부재는 보일 수 없습니다
Edsger Dijkstra가 1970년 "Notes On Structured Programming"에서 남긴 문장이 이 분야의 출발점입니다. 프로그램 테스트는 버그의 존재를 보이는 데 쓸 수 있지만 부재를 보이는 데는 결코 쓸 수 없다는 것입니다.
이 문장은 흔히 테스트 무용론으로 오해되지만 정반대입니다. 테스트가 증명 도구가 아니라 증거 수집 도구라는 뜻입니다. 증거는 얼마나 모았느냐가 아니라 무엇에 대한 증거냐가 중요합니다. 그래서 테스트를 설계하는 일은 개수를 늘리는 일이 아니라, 어떤 주장을 뒷받침할 증거를 모을지 고르는 일이 됩니다.
1편에서 판정을 수행하는 일은 자동화되고 판정을 설계하는 일은 남는다고 했습니다. 테스트는 그 문장의 가장 깨끗한 사례입니다. 좋은 테스트를 고르는 것은 비싸고, 그 테스트를 만 번 돌리는 것은 공짜입니다.
각 테스트에 신뢰 문장을 붙이기
실무로 옮기는 가장 싼 방법이 하나 있습니다. 테스트마다 한 문장을 붙이는 것입니다. 이것이 초록이면 나는 무엇을 안 걱정해도 되는가.
예시 — 테스트에 붙이는 신뢰 문장
test_refund_exceeds_payment_is_rejected
→ 결제액보다 큰 환불이 데이터베이스에 기록되는 일은 없다고 믿어도 된다
test_order_service_returns_200
→ (문장이 안 써짐) 이 테스트가 초록이어도 걱정이 줄어드는 항목이 없다
두 번째 같은 테스트가 생각보다 많습니다. 실행은 하는데 아무것도 주장하지 않는 테스트입니다. 이런 테스트는 없는 것보다 나쁩니다. 개수를 채워서 안심시키고, 그 안심 위에서 판단이 이뤄지기 때문입니다.
문장을 붙이다 보면 부수 효과가 하나 생깁니다. 같은 문장이 여러 테스트에 붙는 것이 보이고, 그건 중복이고, 반대로 아무 테스트도 붙지 않은 중요한 문장이 보입니다. 그 빈자리가 다음에 쓸 테스트입니다.
커버리지가 재는 것과 재지 못하는 것
커버리지는 유용하지만 자주 오독됩니다. 커버리지가 재는 것은 테스트 실행 중에 그 줄을 지나갔는가입니다. 그 줄의 결과를 검사했는가가 아닙니다.
단언문이 하나도 없는 테스트도 커버리지를 올립니다. 예외가 안 나면 통과하기 때문입니다. 그래서 커버리지 숫자는 위쪽으로는 거의 의미가 없고, 아래쪽으로만 의미가 있습니다. 20퍼센트라면 확실히 부족하다는 정보가 있지만, 90퍼센트라는 숫자에는 안전하다는 정보가 없습니다.
더 쓸모 있는 신호는 따로 있습니다. 최근 장애가 났던 코드 경로에 테스트가 있는지, 되돌리기 어려운 동작에 테스트가 있는지, 그리고 불안정 테스트가 몇 개인지입니다. 마지막 것이 특히 중요합니다. 가끔 빨간불이 뜨는 테스트가 있으면 사람들은 빨간불을 무시하는 법을 배우고, 그 학습은 그 테스트에만 적용되지 않습니다.
검증하지 않기로 한 것을 적기
이 글에서 가장 실천하기 어렵고 값어치가 큰 항목입니다. 무엇을 검증했는지가 아니라 무엇을 검증하지 않기로 했는지를 적는 것입니다.
모든 것을 검증할 수는 없으므로 모든 팀은 이미 이 선택을 하고 있습니다. 다만 대개 암묵적으로 하고, 그래서 아무도 그 선택을 검토하지 않습니다. 명시적으로 적어 두면 세 가지가 생깁니다.
첫째, 사고가 났을 때 그것이 알려진 위험이었는지 몰랐던 위험이었는지 구별됩니다. 둘째, 위험 감수 결정이 개인의 판단에서 팀의 결정으로 올라갑니다. 셋째, 검증하지 않기로 한 목록을 사람들에게 보여 주면 반발이 나오고, 그 반발이 진짜 중요한 항목을 알려 줍니다.
형식은 단순해도 됩니다. 이번 변경에서 검증한 것 세 줄, 검증하지 않은 것 세 줄, 검증하지 않은 이유 한 줄이면 충분합니다.
판정 기준이 없는 영역
지금 가장 어려운 영역은 정답표가 없는 쪽입니다. 검색 결과의 품질, 추천의 적절함, 생성된 텍스트의 유용함. 여기서는 통과와 실패로 갈리는 테스트를 쓸 수 없습니다.
이때 필요한 것은 테스트가 아니라 판정의 설계입니다. 세 가지가 축이 됩니다.
먼저 기준 집합입니다. 사람이 판정한 사례를 모아 고정된 세트로 만들어 둡니다. 크지 않아도 되고, 대신 어렵고 논쟁적인 사례가 들어가야 합니다. 쉬운 사례만 모으면 무엇을 바꿔도 점수가 안 움직입니다.
다음은 대리 지표입니다. 직접 재고 싶은 것을 못 재니 옆의 것을 잽니다. 이때 반드시 함께 적어야 할 것이 이 지표가 어떻게 속을 수 있는지입니다. 속는 경로를 미리 적어 두지 않으면, 언젠가 그 경로로 지표가 올라가고 아무도 못 알아챕니다.
마지막은 표본 검토입니다. 자동 판정이 불가능한 부분은 정기적으로 사람이 표본을 봅니다. 이때 중요한 것은 좋은 것을 보는 게 아니라 무작위로 뽑는 것입니다. 골라서 보면 아무것도 안 배웁니다.
직접 해보기
이번 주에 자기가 최근에 쓴 테스트 다섯 개를 열고, 각각에 신뢰 문장을 한 줄씩 붙여 보세요. 안 써지는 것이 하나라도 나오면 그것이 이 글에서 가장 중요한 발견입니다.
- SLO와 에러 예산 계산기 — 안정성을 목표가 아니라 예산으로 다루면 얼마나 검증할 것인가가 계산 문제가 됩니다.
- 논리법 훈련소 — 필요조건과 충분조건을 구별하는 연습이 들어 있습니다. 테스트가 무엇을 보장하는지 정확히 말하는 일과 같은 구조입니다.
안 통하는 경우도 있습니다. 요구가 매주 바뀌는 탐색 단계에서 두꺼운 테스트를 깔면, 테스트가 변경을 막는 벽이 됩니다. 이 단계에서는 되돌릴 수 없는 것들에만 검증을 붙이고 나머지는 비워 두는 편이 낫습니다. 검증의 밀도는 코드의 중요도가 아니라 되돌리기 비용을 따라가야 합니다.
이어서 읽기
- 이 블로그의 관련 글: PoC는 왜 프로덕션에 못 가는가 — 성공 기준, 보안 검토, 인수인계
비싸게 남는 기술 시리즈
참고 자료
- Edsger W. Dijkstra 인용 모음 — Wikiquote — 프로그램 테스트는 버그의 존재를 보일 수 있을 뿐 부재를 보일 수 없다는 문장의 출처는 1970년 "Notes On Structured Programming"(EWD249)입니다. 2026-08-15 확인.
- 신뢰 문장, 명시적 비검증 목록, 판정 설계의 세 축은 위 자료에 나오는 것이 아니라 이 글에서 정리한 절차입니다.
Designing Verification — Tests as Grounds for Trust, Not Pass or Fail
- Green on Its Own Means Nothing
- Presence Can Be Shown, Absence Cannot
- Attach a Trust Sentence to Every Test
- What Coverage Measures and What It Cannot
- Write Down What You Decided Not to Verify
- Domains With No Oracle
- Try It This Week
- Further Reading
- Sources
Green on Its Own Means Nothing
CI is green. So what do you now know?
Plenty of green builds cannot answer that. The passing is there; what the passing guarantees is a sentence nobody holds. In that state a test suite is a gate in the deploy pipeline, not grounds for a judgment. Gates get forgotten once cleared; grounds get pulled back out when something goes wrong.
The difference shows right after a release. When an incident lands, somebody always asks whether the tests caught this. Being able to answer "that area was deliberately outside our verification scope" lets an organization make its next decision. Being able only to say "they should have" guarantees a repeat.
Presence Can Be Shown, Absence Cannot
The starting point for this whole subject is the sentence Edsger Dijkstra left in "Notes On Structured Programming" in 1970: program testing can be used to show the presence of bugs, but never to show their absence.
That line is often misread as an argument against testing. It is the opposite. It says tests are not a proof device but an evidence-collection device. With evidence, what matters is not how much you gathered but what it is evidence for. So designing tests stops being about raising a count and becomes about choosing which claims you intend to support.
Part 1 argued that performing a judgment gets automated while designing the judgment stays. Testing is the cleanest instance of that sentence. Choosing a good test is expensive; running it ten thousand times is free.
Attach a Trust Sentence to Every Test
There is one cheap way to move this into practice: attach a sentence to each test. If this is green, what do I no longer have to worry about?
Example — trust sentences attached to tests
test_refund_exceeds_payment_is_rejected
-> I can trust that a refund larger than the payment never lands in the database
test_order_service_returns_200
-> (no sentence available) nothing gets less worrying when this one is green
The second kind is more common than you would guess: a test that executes without asserting anything. Those are worse than nothing, because they pad the count, produce reassurance, and judgments get made on top of that reassurance.
Writing the sentences produces a side effect. You see the same sentence attached to several tests, which is duplication, and you see important sentences with no test attached at all. Those empty slots are the tests to write next.
What Coverage Measures and What It Cannot
Coverage is useful and routinely misread. What it measures is whether execution passed through a line during the test run. Not whether the result of that line was checked.
A test with no assertions raises coverage, because it passes as long as nothing throws. So the number carries almost no information upward and only carries information downward. Twenty percent tells you something is definitely missing; ninety percent contains no statement that you are safe.
More useful signals exist. Does the code path that caused your last incident have a test. Do the irreversible behaviors have tests. And how many flaky tests are there. The last one matters most: when a test goes red occasionally, people learn to ignore red, and that lesson does not stay confined to the one test.
Write Down What You Decided Not to Verify
This is the hardest item here to practice and the most valuable. Record not what you verified but what you decided not to verify.
Since nothing can be verified exhaustively, every team is already making this choice. They just make it implicitly, which means nobody ever reviews it. Making it explicit produces three things.
First, when an incident hits, you can tell a known risk from an unknown one. Second, the decision to accept a risk moves up from one person's judgment to a team decision. Third, showing people the not-verified list draws objections, and the objections tell you which items actually mattered.
The format can stay simple. Three lines of what this change verified, three lines of what it did not, and one line of why not is enough.
Domains With No Oracle
The hardest territory right now is the one with no answer key: search result quality, whether a recommendation fits, whether generated text is useful. Tests that split into pass and fail are unavailable here.
What you need then is not a test but a designed judgment, on three axes.
First, a reference set. Collect human-judged cases and freeze them into a fixed set. It does not need to be large, but it must contain hard and contested cases. Assemble only easy cases and the score stops moving no matter what you change.
Second, proxy metrics. You cannot measure the thing you want, so you measure something adjacent. What must be written alongside it is how the proxy can be gamed. Fail to write the gaming path in advance and someday the metric climbs along that path with nobody noticing.
Third, sampled review. Where automated judgment is impossible, people look at samples on a schedule. What matters here is drawing at random rather than looking at good ones. Hand-picked review teaches nothing.
Try It This Week
Open five tests you wrote recently and attach a one-line trust sentence to each. If even one of them resists, that is the most important finding this article can give you.
- SLO and Error Budget Calculator — treat reliability as a budget rather than a target and the question of how much to verify becomes an arithmetic problem.
- Logical Reasoning Trainer — includes drills on separating necessary from sufficient conditions, the same structure as saying precisely what a test guarantees.
Where this does not apply: in an exploratory phase where requirements change weekly, a thick test suite becomes a wall against change. There it is better to attach verification only to the irreversible parts and leave the rest bare. Verification density should track the cost of undoing, not the importance of the code.
Further Reading
- Related post on this blog: Why PoCs Never Reach Production — Success Criteria, Security Review, Handover
What Stays Expensive series
- Previous: Reading What Others Wrote — Entering a Codebase and Reverse-Engineering an Undocumented System
- Next: Writing as an Engineering Skill — What Design Docs, Postmortems, and Review Comments Actually Do
Sources
- Edsger W. Dijkstra quotations — Wikiquote — the sentence that program testing shows the presence of bugs but never their absence is sourced to "Notes On Structured Programming" (EWD249), 1970. Read 2026-08-15.
- The trust sentence, the explicit not-verified list, and the three axes of judgment design are not from that source; they are the procedure assembled in this post.