소프트웨어 테스트 전략은 대개 커버리지라는 숫자로 설명되지만, 커버리지는 이 분야 전체에서 정보량이 가장 적은 숫자입니다. 커버리지가 구십 퍼센트인 코드베이스도 가장 많이 쓰이는 경로에서 버그를 그대로 배포할 수 있습니다. 커버리지는 테스트가 도는 동안 어떤 줄이 실행되었는지를 측정할 뿐, 그 줄에 대해 의미 있는 검증이 이루어졌는지는 전혀 측정하지 않기 때문입니다.
자기 테스트 스위트를 신뢰하는 팀은 퍼센트가 가장 높은 팀이 아닙니다. 무언가가 정말로 망가졌을 때 테스트가 빨간불을 켜고, 그 외의 순간에는 조용히 있는 팀입니다. 그리고 이 성질은 돈으로 사기가 훨씬 어렵다는 사실이 드러납니다.
어떤 테스트에 대해서든 물어볼 가치가 있는 질문: 이 테스트가 실패하면, 나는 무엇을 해야 할지 알 수 있을까요? 동작이 바뀌어서 실패하는 테스트는 무언가를 알려줍니다. 구현 세부사항이 옮겨졌기 때문에 실패하는 테스트는 누군가 리팩터링을 했다는 사실만 알려줍니다. 그런 실패가 충분히 쌓이면 팀은 실패 내용을 읽지 않게 되고, 초록불이 뜰 때까지 파이프라인을 다시 돌리기만 합니다.
커버리지가 사람을 오도하는 이유
커버리지는 이 줄이 실행되었는가에 답할 뿐, 이 줄이 올바른가에는 답하지 않습니다. 함수를 호출하기만 하고 아무것도 검증하지 않는 테스트는, 결과의 모든 분기를 확인하는 테스트와 정확히 같은 커버리지를 만들어 냅니다.
이 간극은 실무적인 결과를 낳습니다. 고정된 퍼센트로 목표를 잡으면 그 숫자를 채우기 위해 쓰인 테스트가 어김없이 생겨납니다. 사소한 게터에는 빈틈없는 테스트가 붙고, 분기를 만들기 까다로운 결제 경로에는 아무것도 없는 상태가 됩니다. 숫자는 올라가지만 위험은 조금도 움직이지 않습니다.
커버리지는 한 방향으로만 쓸모가 있습니다. 중요한 영역의 커버리지가 낮다는 것은 행동에 옮길 가치가 있는 진짜 신호입니다. 반면 전체 커버리지가 높다는 것은 아무것도 증명하지 못하며, 이를 진단 도구가 아니라 목표로 다루는 것이야말로 팀이 수천 개의 테스트를 갖고도 아무 확신을 갖지 못하게 되는 전형적인 경로입니다.
소프트웨어 테스트 전략: 어떤 테스트가 값을 하는가
모든 테스트는 자산인 동시에 부채입니다. 유지보수가 필요하고, 스위트를 느리게 만들며, 가끔은 테스트 자체가 틀립니다. 쓸모 있는 질문은 그중 어떤 테스트가 그 비용을 되갚느냐입니다.
단위 테스트는 로직이 정말로 복잡하고 인프라와 무관할 때 값을 합니다. 가격 정책, 날짜 처리, 권한 검사, 파서 같은 것들입니다. 빠르고 정확하며, 설명하는 동작이 실재하기 때문에 리팩터링에서도 살아남습니다.
통합 테스트는 대부분의 팀이 기대하는 것보다 훨씬 크게 값을 합니다. 운영 환경 버그의 다수는 함수 내부가 아니라 경계에 살기 때문입니다. 목 객체에는 통과하지만 실제 데이터베이스에서는 깨지는 쿼리, 선택 필드가 현실에서는 늘 비어 있는 API 같은 것들이죠. 이런 테스트는 더 느리고, 그럴 만한 가치가 있습니다.
엔드투엔드 테스트는 손에 꼽을 정도의 여정에 대해서만 값을 하며, 그 수는 소리 내어 나열할 수 있을 만큼 적어야 합니다. 가입, 구매, 그리고 여러분의 사업이 실제로 하는 그 한 가지. 느리고 잘 깨지고 비싸며, 이런 테스트 이백 개짜리 스위트는 팀이 겪는 고통의 주된 원천이 됩니다.
대부분의 코드베이스가 결국 수렴하는 형태는 많은 단위 테스트, 탄탄한 통합 테스트 층, 그리고 손에 꼽을 만큼의 엔드투엔드 여정입니다. 바로 마틴 파울러가 설명한 테스트 피라미드 입니다. 팀이 틀어지는 지점은 보통 중간층입니다. 단위 테스트도 있고 엔드투엔드 테스트도 있는데, 조각들이 서로 맞물리는지 확인하는 것은 거의 없습니다.
불안정한 테스트는 신뢰의 문제
스무 번 중 한 번 실패하는 테스트는 테스트가 아예 없는 것보다 나쁘며, 그 이유는 기술적이라기보다 행동적입니다.
스위트에 불안정한 테스트가 몇 개만 섞여도 팀은 빨간불이 반드시 고장을 뜻하지는 않는다고 학습합니다. 다시 돌리는 일이 일상이 됩니다. 그러다 진짜 실패도 다시 돌려지고, 세 번째 시도에서 통과하면 누군가 그대로 병합합니다. 스위트는 신호로서 기능하기를 멈췄으면서 시간은 계속 잡아먹습니다.
불안정성은 운영 버그와 같은 우선순위의 결함으로 다루십시오. 파이프라인이 정직하게 초록불이 되도록 해당 테스트를 즉시 격리한 다음, 고치거나 지우십시오. 흔한 원인은 테스트 간 공유 상태, 실제 시간에 대한 의존, 그리고 러너가 보장하지 않는 실행 순서에 기대는 것입니다.
불안정한 테스트를 지우는 것은 정당한 결말입니다. 아무도 믿지 않는 테스트는 어떤 보호도 제공하지 않으며, 없애면 최소한 주의력을 갉아먹는 일은 멈춥니다.
구현이 아니라 동작을 테스트하라
테스트 스위트가 비싸지는 가장 흔한 원인은, 코드가 무엇을 하는지가 아니라 어떻게 동작하는지에 결합된 테스트입니다.
모든 의존성을 목으로 바꾸고 특정 메서드가 특정 인자로 호출되었는지를 검증하면, 동작이 바뀌었든 아니든 리팩터링 때마다 실패하는 테스트가 만들어집니다. 이것은 정확히 거꾸로입니다. 리팩터링이야말로 아무것도 깨지지 않았다고 스위트가 말해 주기를 가장 바라는 순간인데, 대신 손으로 하나씩 헤쳐 나가야 할 오십 건의 실패가 쏟아집니다.
대안은 결과를 검증하는 것입니다. 이 입력이 주어지면 시스템은 이 출력을 내거나 이 상태에 도달한다. 이런 테스트는 내부 재작성에서도 살아남고, 그 말은 위험이 가장 큰 변경을 하는 바로 그 순간에 여러분을 계속 지켜 준다는 뜻입니다.
목은 진짜 경계에서 제 몫을 합니다. 결제 대행사, 이메일 서비스, 느린 것, 그리고 테스트 실행 중에 일어나서는 안 되는 부수 효과를 가진 것들입니다. 자기 코드 내부에서는 대개 돌려주는 것보다 더 많은 비용을 요구합니다.
CI에서 돌아가게 만들기
아무도 기다리지 않는 스위트는 결국 건너뛰어지는 스위트입니다. 전체 실행에 사십 분이 걸린다면 사람들은 푸시하고 그냥 넘어가며, 피드백은 이미 다른 일을 시작한 뒤에야 도착합니다.
나누십시오. 빠른 단위 테스트와 통합 테스트는 푸시마다 돌려 몇 분 안에 답을 주고, 느린 엔드투엔드 여정은 병합 시점이나 예약 실행으로 미루십시오. 이는 저희 2026년 영국 개발팀을 위한 CI/CD 모범 사례 가이드에서 다룬 배포 규율과 같은 논리입니다.
실패를 읽을 수 있게 만드십시오. 무엇을 확인하고 있었는지 알려 주지 않은 채 단지 어떤 단언이 거짓이었다고만 말하는 실패는 매번 십 분의 고고학 작업을 요구합니다. 테스트를 그것이 지키는 동작의 이름으로 부르면, 실패 목록은 그대로 무엇이 깨졌는지에 대한 설명이 됩니다.
그리고 스위트를 결정적으로 유지하십시오. 실제 네트워크 호출 금지, 통제 없이 오늘 날짜에 의존하기 금지, 실행 순서 가정 금지. 비결정적인 테스트는 하나하나가 미래의 불안정한 테스트입니다.
테스트가 없는 코드베이스에서 어디부터 시작할까
과거로 거슬러 올라가 완전한 커버리지를 채우려 하지 마십시오. 노력은 어마어마한데 그 대부분은 아무도 건드리지 않는 코드를 지키는 데 쓰입니다.
버그가 돈을 잃게 만드는 경로부터 시작해 그 주변에 통합 테스트를 먼저 쓰십시오. 작성한 테스트 한 개당 잡아내는 문제가 가장 많기 때문입니다. 그다음에는 버그를 고칠 때마다 테스트를 하나씩 추가하되, 고치기 전에 반드시 실패를 재현하십시오. 그러면 커버리지는 결함이 실제로 발생하는 바로 그 자리에서 자라나고, 이는 위험이 어디에 있는지에 대해 얻을 수 있는 최선의 신호입니다.
Mecanik은 맞춤 소프트웨어 개발 서비스 의 일부로 테스트 전략을 검토하고 구축하며, 대개 어떤 실패가 정말로 아플지를 묻는 것에서 시작합니다. 스위트는 큰데 팀이 여전히 조마조마하게 배포하고 있다면, 문제는 테스트 개수인 경우가 거의 없습니다.
함께 읽기: 정말 읽히는 기술 문서 , 첫 주에 성과를 내는 개발자 온보딩 , 정말로 무언가를 바꾸는 포스트모템 그리고 API 버전 관리: 언제 깨고 어떻게 피할까 .
자주 묻는 질문
테스트 커버리지가 높은 것이 좋은 목표인가요? 그 자체만으로는 아닙니다. 커버리지는 테스트가 도는 동안 어떤 줄이 실행되었는지를 측정할 뿐 그 줄에 대해 의미 있는 검증이 이루어졌는지는 측정하지 않기 때문에, 함수를 호출만 하고 아무것도 검증하지 않는 테스트도 모든 분기를 확인하는 테스트와 동일한 점수를 받습니다. 중요한 경로의 낮은 커버리지는 유용한 신호이지만, 전체 퍼센트가 높다는 사실은 별로 증명해 주는 것이 없습니다.
단위 테스트, 통합 테스트, 엔드투엔드 테스트의 올바른 비율은 무엇인가요? 정말로 복잡한 로직에는 많은 단위 테스트를, 운영 버그 대부분이 경계에 살기 때문에 두툼한 통합 테스트 층을, 그리고 소리 내어 나열할 수 있을 만큼 적은 수의 엔드투엔드 여정을 두십시오. 대부분의 팀은 중간층에서 틀어집니다. 단위 테스트와 엔드투엔드 테스트는 있는데 조각들이 맞물리는지 확인하는 것은 거의 없습니다.
불안정한 테스트는 어떻게 다뤄야 하나요? 운영 버그와 같은 우선순위의 결함으로 다루십시오. 파이프라인이 정직하게 유지되도록 해당 테스트를 즉시 격리한 다음 고치거나 지우십시오. 스위트에 불안정한 테스트가 몇 개만 생겨도 팀은 빨간불이 고장을 뜻하지 않는다고 학습하고, 반사적으로 다시 돌리다가 결국 진짜 실패까지 병합하게 됩니다. 불안정한 테스트를 지우는 것은 정당한 결말입니다.
테스트에서 의존성을 목으로 대체해야 하나요? 진짜 경계라면 그렇습니다. 결제 대행사, 이메일 서비스, 느리거나 부수 효과가 있는 모든 것이 해당합니다. 자기 코드 내부에서는 목이 대개 돌려주는 것보다 더 많은 비용을 요구합니다. 특정 메서드가 특정 인자로 호출되었는지를 검증하면 동작이 바뀌었든 아니든 리팩터링 때마다 테스트가 실패하기 때문입니다.
테스트가 하나도 없는 코드베이스에 테스트를 어떻게 추가하나요? 과거로 거슬러 올라가 완전한 커버리지를 채우려 하지 마십시오. 버그가 돈을 잃게 만드는 경로 주변에 통합 테스트를 쓰십시오. 작성한 테스트 한 개당 잡아내는 문제가 가장 많기 때문입니다. 그다음 버그를 고칠 때마다 테스트를 하나씩 추가하고 고치기 전에 실패를 재현하면, 커버리지는 결함이 실제로 발생하는 바로 그 자리에서 자라납니다.
댓글