Uncle Bob의 "나는 더 이상 코드를 읽지 않는다"가 테스트에 대해 내게 가르쳐준 것
요약
Uncle Bob의 관점을 통해 AI 에이전트가 작성한 코드를 검증하는 방법론을 다룹니다. 코드를 직접 읽는 대신 방대한 양의 테스트를 통해 AI 생성 코드의 정확성을 확보하는 전략을 제시합니다.
핵심 포인트
- AI 에이전트 시대에는 코드 독해보다 테스트를 통한 검증이 중요해짐
- 단위, 통합, 기능 테스트를 활용한 다각도 검증 필요
- 스모크, 회귀, UAT 등 다양한 테스트 유형의 활용
- AI 생성 코드의 소유권과 생산성 사이의 균형 문제
저는 _Clean Code_의 저자인 Uncle Bob이 X(link)에 올린, 수천 건의 리포스트를 기록한 게시물을 접했습니다. 그는 이제 더 이상 코드를 읽지 않는다고 말합니다. 네, 개발자는 코드를 쓰는 것보다 훨씬 더 자주 읽기 때문에 당신의 코드는 읽기 쉬워야 한다고 책에 썼던 바로 그 사람이 말입니다.
그렇다면 Uncle Bob의 방법은 무엇일까요? AI 에이전트(AI agents)가 작성한 코드를 읽는 대신, 그는 에이전트에게 해당 코드 주변에 엄청난 양의 테스트(tests)를 만들라고 요청합니다.
물론 어떤 사람들은 동의하고 어떤 사람들은 동의하지 않습니다. 만약 코드를 읽지 않는다면, 소유권(ownership)의 의미가 다소 모호해질 수 있습니다. 하지만 모든 코드를 다 읽는다면, 생산성(productivity)의 대부분을 잃게 됩니다. 저는 여기서 판단을 내리거나 저의 "가치 있는" 의견을 제공할 수는 없습니다. 저는 이곳저곳에서 반복적으로 등장하는 주제 때문에 이 글을 쓰기로 결정했습니다 (저는 글을 자주 쓰는 편은 아닙니다).
현재 많은 논의가 AI 검증(validating AI)에 대해 이루어지고 있습니다. 물론 생물학(biology) 및 생물정보학(bioinformatics)에서의 검증은 소프트웨어 공학(software engineering)에서의 검증과 같지는 않지만, 가치 있는 중첩 영역이 있다고 느낍니다.
해당 스레드와 리포스트들은 Uncle Bob을 포함한 프로그래머들이 자신의 에이전트를 제어하기 위해 사용하는 다양한 테스트들을 언급합니다. 이를 깊이 파고들면서 생물정보학자(bioinformatician)로서 제가 예상했던 것보다 훨씬 더 큰 테스트의 세계가 열렸음을 알게 되었습니다.
정확성 (Correctness)
-
단위 테스트 (Unit tests) – 개별 함수나 코드의 작은 단위들을 격리된 상태에서 확인합니다. 로직, 에러 처리, 그리고 엣지 케이스 (edge cases)를 검사합니다. 가장 잘 알려진 소프트웨어 테스트 유형입니다.
-
통합 테스트 (Integration tests) – 분리된 모듈들이 함께 올바르게 작동하는지 확인합니다 (예: API가 데이터베이스와 통신하는 과정).
-
기능 테스트 (Functional tests) – 블랙박스 (black-box) 관점 및 요구사항 관점에서 소프트웨어가 의도된 대로 동작하는지 검증합니다. 소프트웨어에 입력 데이터를 제공하고 그 출력을 확인하는 방식입니다. 예를 들어, 새로운 SNV 호출 파이프라인을 작성했다면, 참조 유전체에 시퀀싱 데이터를 입력하고 파이프라인이 요구되는 민감도 (sensitivity)와 특이도 (specificity)를 갖춘 기지의 SNV들을 출력하는지 확인하는 것입니다.
기능 테스트에는 알아둘 가치가 있는 몇 가지 공통적인 하위 유형이 있습니다:- 스모크 테스트 (Smoke tests) – 기본 기능이 작동하는지 빠르게 확인하는 테스트로, 예를 들어 기본 파라미터로 소프트웨어를 실행해 보는 것이 있습니다.
- 회귀 테스트 (Regression tests) – 최근의 업데이트가 기존 기능을 망가뜨리지 않았는지 확인합니다.
- 사용자 수용 테스트 (User Acceptance Testing, UAT) – 실제 사용자들이 자신의 요구사항이 충족되었는지 확인하는 과정입니다. 여기서 "실제 사용자"란 GitHub에서 도구를 다운로드하는 커뮤니티 과학자일 수도 있고, 얼리 액세스 (early-access) 또는 공동 개발 프로그램에 참여하는 선정된 파트너일 수도 있습니다. 다른 카테고리와 마찬가지로 UAT에도 자체적인 하위 영역이 있습니다: 알파 테스트 (alpha testing), 베타 테스트 (beta testing), 비즈니스 가치 검증 (validation of business value), 소프트웨어가 계약 기준을 충족하는지에 대한 검증, 그리고 규제 요구사항 (GDPR, HIPAA, FDA) 충족 여부에 대한 검증 등이 포함됩니다. 참고: 이름은 비슷하지만, 아래에 설명할 수용 테스트 (acceptance tests)는 개발자나 QA가 수행하는 반면, UAT는 실제 사용 사례에서 실제 사용자에 의해 수행됩니다.
-
수용 테스트 (Acceptance tests) / 거킨 테스트 (Gherkin tests) – 전체 기능이 구축 목적이었던 비즈니스 요구사항을 충족하는지 확인합니다. 거킨 테스트는 구조화되고 사람이 읽기 쉬운 형식 (Given/When/Then)으로 작성되어, 엔지니어가 아닌 사람들도 코드를 읽지 않고 의도된 동작을 검토할 수 있습니다: _Given_은 문맥 (context)을 설명하고, _When_은 소프트웨어에 의해 포착된 특정 사용자 행동이나 이벤트를 설명하며, _Then_은 예상되는 결과를 설명합니다.
-
Truth-set / golden-dataset testing (진실 집합 / 골든 데이터셋 테스트) – Uncle Bob의 이야기는 아니지만, 저의 생물정보학 (bioinformatics) 경험에서 나온 것입니다. 생물정보학, 특히 임상 생물정보학에서는 주로 wet lab (습식 실험실)에서 일어나는 일들과 관련된 여러 가지 이유로 인해 합성 테스트 (synthetic tests)를 생성하기가 어렵습니다. 따라서 생물학은 ground-truth (실제 정답) 샘플에 크게 의존합니다. 이는 별도의 포스트로 다룰 가치가 있지만, NGS (저의 주요 전문 분야)를 위한 좋은 개요 자료는 MDIC SRS Report: Somatic Variant Reference Samples for NGS입니다. 2019년 자료라 다소 오래되었을 수 있고 — 분명히 많은 링크가 깨져 있겠지만 — 저는 최근까지도 설명된 샘플들을 다루어 왔습니다. 이 보고서는 다음과 같은 여러 클래스의 참조 물질 (reference material)을 설명합니다:
- Synthetic DNA (합성 DNA) – 미리 정의된 유전적 변이 (genetic variants)를 가진 합성 DNA 서열을 특성이 잘 규명된 DNA에 추가한 것입니다.
- Genomic DNA (게놈 DNA) – 특성이 규명된 변이를 가진 DNA 물질입니다.
- Cell-free DNA (세포 유리 DNA) – 혈액 샘플에서 추출한 DNA 물질입니다.
- Human cell lines (인간 세포주) – 광범위하게 연구된 불멸화된 세포주입니다. Genome In A Bottle (GIAB)이 아마 가장 유명할 것이며, 고품질 DNA를 통해 시스템을 평가하는 데 사용할 수 있습니다.
- Tissue / Formalin-fixed paraffin-embedded (FFPE) (조직 / 포르말린 고정 파라핀 매립) – 많은 임상 샘플이 주로 조직학적 이유로 FFPE로 보존됩니다. 그러나 NGS의 경우, FFPE 샘플 준비 과정은 매우 해로워(deleterious) 결과적으로 DNA가 심하게 단편화되고 손상됩니다. 하지만 FFPE의 임상적 중요성 때문에, 많은 생물정보학 NGS 워크플로우 (workflows)는 FFPE 샘플로 테스트되어야 합니다.
- Orthogonal validation tests (직교 검증 테스트) – 저의 생물정보학적 경험이 또 하나 반영된 부분입니다. 상용화된 양질의 규명된 샘플이 없을 때, 출력을 검증하는 또 다른 방법은 두 가지 이상의 독립적인 방법과 대조하여 확인하는 것입니다. 예를 들어: NGS, Sanger sequencing (생거 시퀀싱), long-read technologies (롱리드 기술), optical mapping (광학 매핑), cytogenetics (세포 유전학) 등이 있습니다.
Robustness Under Stress (스트레스 상황에서의 견고성)
- 성능 테스트 (Performance tests) – 회귀 (regressions)를 포착하기 위해 예상되는 부하 또는 피크 부하 상황에서 응답 시간, 처리량(throughput), 리소스 사용량을 측정합니다. 이는 클라우드 컴퓨팅에서 특히 중요한데, 컴퓨팅 리소스를 과대평가하면 과다 지불로 이어지고, 과소평가하면 시스템 충돌로 이어질 수 있기 때문입니다. 생물정보학자(bioinformatician)로서 제가 가장 관련이 있다고 느끼는 하위 유형은 다음과 같습니다:
- 부하 테스트 (Load testing) – 성능 테스트의 가장 단순한 형태입니다. 정상적인 부하 상황에서 시스템(저의 경우 생물정보학 파이프라인)이 어떻게 작동하는지 확인합니다. 예를 들어, 회사가 하루에 샘플당 약 1,000만(10M) 개의 리드 쌍(read pairs)을 가진 100개의 NGS 샘플을 처리할 것으로 예상하는 경우입니다.
- 스트레스 테스트 (Stress testing) – 정상 부하를 초과하는 상황에서 시스템이 어떻게 작동할까요? 만약 어느 날 10,000개의 샘플을 처리해야 하거나, 각 샘플이 1억(100M) 개의 리드를 가지고 있다면 어떻게 될까요?
- 고문 테스트 (Torture tests) – 시스템이 실제로 어디에서 고장 나는지 찾기 위해 극단적인 고부하 또는 엣지 케이스(edge-case) 조건으로 시스템을 몰아붙입니다. 여러분의 파이프라인은 1억(100M) 개의 리드를 가진 샘플에서 깨질까요? 아니면 2억(200M) 개의 리드를 가진 샘플에서 깨질까요?
이러한 테스트 비용에 대한 주의 사항(FinOps — 제가 최근에 익힌 새로운 용어 중 하나입니다): 클라우드 환경에서는 너무 많은 시나리오를 테스트하면 비용이 많이 들 수 있으며, 오랫동안 눈에 띄지 않은 채 실행되는 중단된 프로세스를 놓치는 것 또한 비용이 많이 들 수 있습니다. 로컬 HPC 사용자 및 DevOps 팀 또한 노드에 과부하를 주어 다른 작업들을 위한 대기열(queue)을 길게 만들면 상당히 화가 날 수 있습니다.
엄격성 점검 (Rigor Checks, 테스트 자체를 테스트하기)
- 변이 테스트 (Mutation testing) – 코드에 의도적으로 작은 버그를 주입하여 기존 테스트 스위트(test suite)가 이를 잡아내는지 확인합니다. 예를 들어, 상수 값을 바꾸거나, 결정 로직(decision logic)을 변경하거나, 코드 구조를 건너뛰는 방식 등이 있습니다. 만약 테스트가 여전히 통과된다면, 해당 스위트는 충분히 엄격하지 않은 것입니다.
- 테스트 커버리지 (Test coverage) – 코드베이스의 몇 퍼센트가 실제로 테스트에 의해 실행되는지를 측정하여, 테스트되지 않은 영역을 표시합니다. 물론, 커버리지 수치 자체보다 테스트의 품질이 더 중요합니다.
- 재현성 / 결정론적 테스트 (Reproducibility / determinism testing) – 이 내용은 스레드에 없었지만, 생물정보학(bioinformatics)과 AI 모두에서 중요하다고 생각합니다. 확률론적 접근 방식(probabilistic approaches)을 사용할 때는 출력이 안정적인지 확인하기 위해 입력을 여러 번 테스트해야 합니다. AI/ML은 본질적으로 대부분 확률론적이며, 생물정보학은 명시적으로 AI/ML을 사용하지 않더라도 샘플링이나 다른 확률론적 기법을 포함할 수 있습니다.
변동성의 또 다른 원인은 소프트웨어나 패키지 버전을 전환하는 것입니다. 마이너 버전 업데이트만으로도 결과가 극적으로 변할 수 있습니다. 임상 테스트를 위한 CLIA/CAP/CLEP 규정은 소프트웨어 변경 시 재평가를 요구합니다.
비기능적 및 시스템적 점검 (Non-Functional and Systemic Checks)
- 보안 테스트 (Security testing) – 기능적 정확성보다는 취약점을 찾습니다: 민감한 데이터가 안전한가, 사용자 권한이 예상대로 작동하는가, 시스템이 코드 인젝션 (Code injection)으로부터 보호되고 있는가?
- 아키텍처 점검 (Architecture checks) – 코드가 정의된 아키텍처 경계와 의존성 규칙을 준수하는지 자동화된 방식으로 검증합니다: 모든 모듈이 의도한 대로 연결되어 있는가, 명명 규칙 (Naming conventions)을 따르고 있는가, 패키지 사용이 일관적인가 (예: 동일한 목적을 위해 하나의 Python 프로젝트에서
os와pathlib를 모두 사용하고 있지는 않은가), 의존성 그래프 (Dependency graph)에 순환이 없는가? - 품질 지표 (Quality metrics) – 한 줄씩 읽지 않고도 유지보수 가능성을 암시하는 일반적인 코드 건강 지표 (순환 복잡도 (Cyclomatic complexity), 의존성 구조, 모듈 크기)입니다. 몇 가지 흥미로운 지표는 다음과 같습니다:
- 결함 밀도 (Defect density) – 코드 1,000줄당 발견된 버그의 수.
- 결함 유출률 (Defect escape rate) – 테스트 단계 대비 운영 환경 (Production)에서 발견된 버그의 비율.
- 결함 해결 시간 (Defect resolution time) – 버그를 수정하는 데 소요되는 시간.
Google의 DORA (DevOps Research and Assessment) 그룹은 이와 관련이 있지만 구별되는 지표 세트를 제안했습니다. 소프트웨어 테스트 품질 지표가 릴리스 전 소프트웨어의 성공을 예측하려 한다면, DORA 지표는 코드가 운영 환경에 도달한 이후의 소프트웨어 인도 성능을 평가합니다. DORA는 지표를 처리량 (Throughput)과 안정성 (Stability)으로 분류합니다:
처리량 (Throughput)
- 리드 타임 (Lead time) – 커밋된 변경 사항을 운영 환경에 반영하는 데 필요한 시간.
- 배포 빈도 (Deployment frequency) – 주어진 기간 내의 운영 환경 릴리스 횟수.
- 배포 실패 복구 시간 (Failed Deployment Recovery Time) – 결함 해결 시간의 대칭적인 개념이지만, 운영 환경에서 측정됩니다. 제가 보기에 이는 처리량과 안정성 모두에 걸쳐 있습니다.
안정성 (Stability)
- 변경 실패율 (Change failure rate) – 결함 유출률 (defect escape rate)의 형제 격인 지표로, 프로덕션(production) 환경에 도달한 후 즉각적인 개입이 필요한 배포/릴리스의 비율을 의미합니다.
- 배포 재작업률 (Deployment rework rate) – 계획되지 않았으나 프로덕션 장애(production incidents)로 인해 발생한 프로덕션 릴리스의 비중입니다.
탐색적 정확성 (Exploratory Correctness)
- 속성 테스트 (Property tests) – 특정 입출력 쌍을 확인하는 대신, 코드가 항상 만족해야 하는 일반적인 불변량 (invariants)을 정의한 다음, 이를 깨뜨리기 위해 다양하고 많은 입력을 생성하여 테스트합니다.
제게 속성 테스트 (property tests)는 위에서 언급한 "정확성" 섹션의 단위 테스트 (unit tests), 통합 테스트 (integration tests), 기능 테스트 (functional tests)와 유사하게 느껴집니다. 주요한 사고방식의 전환은 함수가 어떤 정확한 출력을 생성해야 하는지가 아니라, 함수가 어떤 속성 (properties) 을 가져야 하는지를 생각하는 것입니다. 예를 들어, DNA 모티프 (motif)를 찾는 함수를 테스트한다고 가정해 봅시다. 단위 테스트 (unit test)에서는 모티프를 포함하는 DNA 단편과 포함하지 않는 단편과 같은 엣지 케이스 (edge cases)를 고민할 것입니다. 반면 속성 테스트 (property test)에서는 대신 불변량 (invariant)을 정의합니다: 만약 입력 DNA가 모티프를 포함하면 함수는 그 좌표를 반환하고, 포함하지 않으면 -1을 반환한다는 식입니다. 그런 다음 이 규칙을 따르는 무작위 DNA/모티프 쌍을 생성하여 함수가 얼마나 자주 깨지는지 확인합니다.
프로세스 및 인간 계층 (Process and Human Layer)
- QA 절차 (QA procedures) – 사용자/제품 관점에서 소프트웨어를 점검하는, 더 광범위하고 종종 수동으로 이루어지는 품질 워크플로우입니다.
- 생성된 산출물 검토 (Generated artifacts review) – 정확성의 또 다른 대리 지표로서, 에이전트(agent)가 코드와 관련하여 생성하는 문서, 설정, 다이어그램 등을 검토하는 것입니다. 이는 결과물에 대한 일종의 건전성 검사 (sanity check)입니다.
저는 이 모든 테스트를 사용해 본 것은 아니며, 이 모든 것이 전형적인 생물정보학 (bioinformatics) 파이프라인에 속하는지도 확실하지 않습니다. 비록 생물정보학이 소프트웨어 엔지니어링 관행과 수렴하려고 항상 노력하고 있기는 하지만 말입니다. 그럼에도 불구하고, 이러한 개념들에 익숙해지는 것은 소프트웨어 팀 및 AI 코딩 에이전트(AI coding agents) 모두와 협업하는 데 매우 가치 있다고 생각합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기