초SSS급 개발자는 테스트를 작성한다

리포트용 SQL 빌더 하나를 믿을 수 있게 만들기까지

이승재 · NHN AD
플랫폼서비스랩 · 2026년 7월 · 약 8분

들어가며

안녕하세요. 초SSS급 개발자가 되고 싶은 이승재입니다. 이번 글은 광고 리포트를 만드는 SQL 빌더 하나를, 믿을 수 있게 만들기까지의 기록입니다.

자연어가 리포트가 되기까지

제가 다루는 서비스에는 SQL을 이용해 리포트를 만드는 빌더가 있습니다. 사용자가 “지난달 캠페인별 전환수 보여줘”처럼 자연어로 요청하면, AI 에이전트가 그 문장을 해석해 차원·지표·기간·필터를 파라미터로 뽑아냅니다. 빌더는 그 파라미터를 받아 Athena에서 돌릴 SQL을 절 단위로 조립하고, 실행 결과를 리포트로 돌려줍니다. 자연어 한 문장이 SQL이 되어, 곧바로 광고주가 보는 숫자가 되는 셈입니다.

자연어 질문 차원·지표·기간·필터 AI 에이전트 파라미터 추출·정규화 SQL 빌더 절 단위 조립 쿼리를 절 단위로 따로 만들어 조립 SELECT GROUP BY WHERE HAVING ORDER BY LIMIT Athena 실행·캐싱 리포트 숫자 반환
자연어 요청이 AI 에이전트를 거쳐 SQL로 조립되고, Athena에서 실행되어 리포트가 된다

문제는, 이런 코드는 틀려도 티가 안 난다는 점입니다. SQL 문법이 깨지는 게 아니라면 쿼리는 멀쩡히 돌고, 표도 예쁘게 채워집니다. 매출이 중복 집계돼 두 배로 부풀어도 아무도 눈치채지 못하고 숫자만 조용히 틀립니다.

리포트 결과 쿼리 성공 ✓ 항목 매출 실제 매출 ₩1,000,000 리포트 매출 ₩2,000,000 중복 집계 ×2 SQL 문법은 멀쩡 — 값만 조용히 틀림 리포트 매출이 왜 실제랑 일치하지 않지? 광고주
SQL은 성공했지만 매출이 두 배로 — 문법이 아니라 값이 조용히 틀린다

그래서 저는 이 빌더를 믿을 수 있게 만드는 테스트를 고민했습니다. 이 글은 그 고민의 기록입니다.

테스트에 대한 고찰

제가 생각하는 테스트는 크게 둘로 나뉩니다. 유닛 테스트와 통합 테스트입니다. 둘 중 뭐가 더 중요하냐고 묻는다면, 저는 둘 다 중요하다고 답하겠습니다. 목적이 서로 다르기 때문입니다.

유닛 테스트는 계약에 대한 검증입니다. ‘이 입력을 주면 이 출력을 준다’는 계약, 곧 스펙을 확인합니다. 실제 인프라를 끌어오지 않아 비용 부담이 적고 빠릅니다만 그 환경은 실제와 다릅니다. 프로덕션에서는 인프라를 호출하고, 모듈끼리 협응하고, 네트워크를 타고 밖으로 나갑니다. 하지만 유닛 테스트는 그렇지 않습니다. 게다가 유닛 테스트는 대부분 성공하기 마련입니다. 애초에 스펙을 확인하려고 성공 케이스와 실패 케이스를 나눠 그렇게 mock으로 만들어 두니까요. 그래서 유닛 테스트의 역할은 스펙 검증과, 코드 수정 시 변경의 전파를 잡는 데 있습니다.

반면 통합 테스트는 정도의 차이는 있지만 실제 프로덕션 환경과 최대한 유사하게 맞추려 합니다. 실제 동작을 테스트할 수 있지만 그만큼 무겁고, 경우에 따라 과금이 발생하기도 합니다.

실제 코드가 프로덕션으로 나가려면 통합 테스트가 반드시 필요합니다. 그리고 이 둘은 언제나 함께 개발되어야, 비로소 테스트 코드가 제값을 합니다.

유닛 테스트 계약 · 스펙 검증 · mock으로 성공·실패 분리 · 인프라 없이 싸게 · 빠르게 돌린다 변경의 전파를 잡는다 통합 테스트 프로덕션에 가까이 · 인프라 호출 · 모듈끼리 협응 · 네트워크 타고 밖으로 프로덕션 반출의 필수 조건 둘은 언제나 함께 개발되어야 테스트가 제값을 한다
유닛은 계약을 싸게 확인하고, 통합은 프로덕션의 협응을 확인한다 · 둘은 함께 간다

데이터가 없는데 어떻게 테스트해요

막상 테스트를 하려니 문제가 있었습니다. S3·Athena 같은 AWS 인프라는 갖춰져 있었지만, 정작 그 위에서 테스트에 쓸 데이터가 없었습니다.

그래서 mock 데이터로 시작했습니다. 작은 가짜 데이터를 만들어 넣고 그 값을 pandas로 검산해 둔 다음, 같은 입력을 빌더에 넣어 만들어진 SQL의 결과와 맞춰 봤습니다. 이렇게 빌더가 뽑은 숫자를 빌더로 확인하지 않고 독립된 계산과 대조하는 방식을 테스트 오라클 기법이라고 합니다. 두 경로는 완전히 독립적이며 셀 단위로 한 칸이라도 어긋나면 실패입니다.

빌더의 SQL 실 Athena 실행 원본 데이터 pandas 독립 재계산 결과 A 결과 B 셀 단위 대조 0 불일치 = 통과
정답을 아는 독립 계산이 따로 있어야 검증이 성립한다 · 두 경로가 셀 하나까지 같아야 통과

이 단계에서는 데이터의 정합성도 살폈지만, 주로 확인하려던 건 스펙이었습니다. SQL이 깨지지는 않는지, 빌더가 조건에 따라 분기를 제대로 타는지, 테이블을 옳게 고르는지. 빌더 로직이 뽑아내는 SQL의 모양을 검증하려 했습니다.

실데이터가 적재되는 순간에도 새로운 테스트 오라클을 만들면 됐습니다. 다행히 실데이터 기반으로도 잘 검증해 주었지만 이내 다른 문제를 맞닥뜨리게 됩니다. 요구사항은 언제나 변하고, 버그는 발견됩니다. 그때마다 코드를 리팩토링하고, 테스트를 다시 정비하는 과정은 너무 반복적이고 지루하다는 겁니다.

어떻게 매번 사람이 해요

테스트를 설계하며 세 가지를 중요하게 뒀습니다. 첫째, 언제 돌려도 재현될 것. 둘째, 무엇을 왜 고쳤는지 추적될 것. 셋째, 이 둘을 사람 손이 아니라 자동으로 굴릴 것.

01 재현 고정 데이터셋 회귀 테스트 스크립트 영구화 언제 돌려도 같은 결과 02 추적 서사 문서 테스트셋 문서 append only 무엇을 왜 고쳤는지 남는다 03 자동화 테스트 에이전트 스킬 같은 절차 반복 사람 손 없이 굴러간다
재현 · 추적 · 자동화 — 테스트 설계에서 붙든 세 가지

첫 번째 원칙, 재현. 검증에 쓴 데이터셋을 고정해 두고, 그 위에서 케이스 전부를 매번 다시 돌립니다. 빌더 로직을 건드릴 때마다 같은 데이터에 같은 케이스를 던져, 예전에 맞던 것이 지금도 맞는지 확인합니다. 일종의 회귀 테스트입니다. 검증 스크립트도 저장소 안에 영구화해, 파일 이름만으로 그때 그 라운드를 그대로 다시 돌릴 수 있게 했습니다.

두 번째 원칙, 추적. 테스트를 돌릴 때마다 검증 문서를 남겼습니다. 하나는 서사입니다. 목표, 데이터 출처, 산식, 라운드별 결과, 실패했을 때의 원인과 고친 과정. 다른 하나는 테스트셋입니다. 여태 돌린 SQL 전문과 각 결과, 재현 방법. 둘 다 지우지 않고 append만 합니다.

검증 문서 · 서사 목표 · 데이터 출처 · 산식 라운드별 결과와 판정 실패 원인 · 스스로 고친 과정 append only SQL 문서 · 테스트셋 돌린 SQL 전문 각 결과 재현 방법 append only R1 R2 R3 R9 라운드가 쌓일수록 문서도 함께 쌓인다
서사와 테스트셋을 지우지 않고 쌓는다 · 파일 이름만으로 그때 그 라운드를 다시 돌릴 수 있게

세 번째 원칙, 자동화. 여기까지를 매번 사람이 손으로 할 수는 없습니다. 그래서 SQL 빌더 전용 테스트 에이전트와 스킬을 만들었습니다. 에이전트가 케이스를 펼치고, 스킬이 검증 절차를 매번 같은 순서로 밟습니다. 재현과 추적이 사람 의지가 아니라 도구의 기본 동작이 됩니다.

① 오라클 재계산 S3 원본 → pandas 독립 재계산 verify_core.py · 코드 무의존 ② 산출물 실행 sql_builder → Athena 실행 결과 재사용 캐시 ③ 셀 단위 대조 키 1:1 · 3축 값 · CASE 파싱 · 컬럼 순서 ④ 회귀 스위트 QA마다 전용 라운드 추가 전체 스위트 재실행 ⑤ append 문서 검증.md 서사 · SQL.md 테스트셋 · QA 트래커 sql_builder를 바꿀 때마다 전체 스위트를 반복 · 스위트는 QA마다 늘어난다
검증 스킬 구조 — 독립 오라클과 빌더 실행을 셀 단위로 대조하고, QA마다 전용 라운드를 더해 전체를 회귀로 재실행하며 결과를 문서에 append한다

마치며

테스트를 쓰는 일은 구현보다 언제나 더 어려운 것 같습니다. 잘 쓰인 테스트는 그것만 봐도 본래 코드를 유추할 수 있고, 그 자체로 실행 가능한 스펙이 됩니다.

굳이 개발자의 경지를 나눈다면, 유의미한 테스트 코드를 잘 쓰는 개발자야말로 앞서 말한 초SSS급 개발자가 아닐까 싶습니다.

그렇기 때문에 요즘은 어떻게 테스트할 것인가, 어떻게 검증할 것인가에 대해 많은 고민을 하고 있습니다.

읽어주셔서 감사합니다.

참조