정답 하나로 매긴 테스트 점수, 다시 재니 반토막

AI가 만든 테스트를 정답 구현 하나로만 채점하면 품질이 부풀려진다. 여러 유효·무효 구현으로 다시 재니 가장 좋은 모델도 59.67%에서 28.00%로 떨어졌다.
English  ·  日本語  ·  한국어
← 목록으로
온
온틀 리서치
2026년 10월 10일 수정 2026.10.17 읽기 14분 조회 2

코딩 에이전트는 코드만 짜지 않는다. 그 코드가 맞는지 확인할 테스트도 같이 만든다. 테스트가 초록불이면 "통과, 완료." 개발 현장에서 이 초록불은 신뢰의 증표다.

문제는 이 초록불이 켜지는 방식이다. 보통은 '정답 구현 하나'에 테스트를 돌려서, 통과하면 합격으로 친다. 그런데 같은 요구사항을 만족하는 올바른 코드는 대개 여러 개다. 그럴듯하게 틀린 코드도 여러 개다. 정답 하나만 보고 테스트를 믿어도 괜찮을까?

최근 한 연구가 바로 이 질문을 들고 테스트를 다시 채점했다. 정답 하나가 아니라 여러 구현에 두루 돌려 본 것이다. 가장 성적이 좋던 모델의 합격률이 크게 꺾였다. 테스트는 한 글자도 바꾸지 않았다. 재는 방식만 바꿨을 뿐이다.

왜 정답 하나로는 부족한가

좋은 테스트는 두 가지를 해낸다. 버그가 있는 코드(고치기 전)에서는 실패해서 버그를 드러내고, 제대로 고친 코드에서는 통과한다. 업계 관행은 이 두 조건을 '정답 하나'에 대고 확인한다. 고치기 전엔 실패, 정답 구현(gold solution)에선 통과. 그러면 합격. 이걸 단일 레퍼런스 평가(single reference evaluation)라고 부른다.

여기에 함정이 있다. 채용 시험을 모범답안 한 부에만 맞춰 출제하는 상황을 떠올려 보자. 그 모범답안의 스타일에만 들어맞는 문제가 되면, 똑같이 정답을 썼지만 다르게 푼 지원자는 떨어뜨린다. 반대로 그럴듯하게 틀린 지원자는 걸러내지 못하고 통과시킨다. 테스트도 똑같다. 앞의 경우는 특정 구현 방식에 필요 이상으로 매달려 다른 올바른 코드를 깨뜨리는 것이고, 뒤의 경우는 요구된 동작을 실제로는 검사하지 못하는 것이다. 둘 다 단일 레퍼런스 평가는 '합격' 도장을 찍어 준다.

그러니 진짜 물어야 할 건 이것이다. 이 테스트는 구현이 달라져도 믿을 만한가? 여러 올바른 구현에는 두루 통하면서, 명세를 어긴 구현에는 민감해야 한다. 이 연구의 핵심은 이 오래된 요구를 평가 지표 자체에 집어넣은 데 있다.

더 엄격하게 재는 법: 구현 패널

방법은 단순하다. 테스트를 정답 하나가 아니라 '구현 패널'에 대고 채점한다. 과제마다 패널을 세운다. 기준이 되는 레퍼런스 1개, 방식은 다르지만 올바른 구현 4개, 틀린 구현 5개. 과제 300개에 각각 10개 구현이니, 라벨이 붙은 구현이 3000개다. 유효와 무효가 정확히 반반이다. 테스트를 만들 땐 이 패널을 숨긴다. 에이전트는 요구사항만 보고 테스트를 짜고, 채점은 패널 전체에 대고 한다.

눈여겨볼 대목은 틀린 구현을 만드는 방식이다. 코드에 인위적인 흠집을 내 가짜 결함을 찍어내는(변이) 대신, 능력이 제각각인 여러 모델이 실제로 풀다가 낸 오답을 모았다. 진짜로 저지르는 실수에 가깝게 가려는 선택이다. 비슷한 구현만 모여 패널이 너무 쉬워지지 않도록, 서로 충분히 다른 것들만 남기는 필터도 걸었다.

채점은 네 개의 숫자로 나뉜다.

  • RAR: 기존 방식 점수. 정답 하나에 대고 잰 성공률이다.
  • VAR: 다른 올바른 구현들을 얼마나 받아들이는가.
  • IRR: 틀린 구현들을 얼마나 걸러내는가.
  • JSF: 이 셋을 한꺼번에 요구한다. 고치기 전 코드에서 실패하고, 선택된 올바른 구현을 모두 통과시키고, 틀린 구현을 모두 거부해야 1점. 하나라도 어긋나면 0점. 전부 아니면 전무다.

이 설계의 영리함은 따로 있다. 패널을 레퍼런스 1개로 줄이면 JSF는 정확히 옛날 점수(RAR)로 되돌아간다. 기존 방식은 틀린 게 아니라, 새 잣대의 가장 헐거운 특수 케이스다. 그리고 JSF는 구조적으로 RAR을 넘을 수 없다. 둘의 격차가 곧 '정답 하나는 통과했지만 다른 구현 앞에서 무너진 테스트'의 몫이다.

숫자로 보면 격차가 분명하다. 1위 구성조차 기존 점수(RAR)는 59.67%였지만, JSF는 28.00%였다. 정답 하나엔 60% 가까이 통과하던 테스트가, 조건을 더하자 절반 이하로 꺾인 것이다. 패밀리별 최고 구성들의 JSF는 그 28.00%에서 11.33%까지 흩어져 있다. 앞선 모델에서도 '구현이 달라져도 믿을 수 있는 테스트 만들기'는 아직 풀리지 않았다는 뜻이다.

세부엔 흥미로운 비대칭이 있다. 6개 구성 중 5개가 유효 수용(VAR)보다 무효 거부(IRR)를 잘했다. 틀린 구현을 걸러내긴 쉬운데, 올바른 다른 구현을 '이것도 정답'으로 받아들이는 게 더 어렵다는 뜻이다. 한 구성은 무효 거부가 86.33%로 가장 높지만 유효 수용(50.33%)과 JSF(11.33%)는 꼴찌였다. 걸러내기만 잘해선 좋은 테스트가 못 된다. 반대로 어떤 구성은 유효 수용 77.00%로 잘 받아들이지만 무효 거부(66.00%)는 상대적으로 무뎠다.

한 가지는 분명히 해 두자. JSF는 '선택된 그 패널'에 대한 점수다. 세상 모든 올바른·틀린 구현에 대한 절대 보증이 아니다. 실제로 과제를 추려 패널을 키워 봤더니, 레퍼런스만 세웠을 때 평균 49.33%였던 점수가 유효·무효 구현을 번갈아 9개 더 세우자 24.33%까지 내려갔다. 테스트는 그대로다. 패널이 깐깐해질수록 점수는 깎인다. "JSF 28%"를 절대 등급이 아니라 이 구성에서의 측정치로 읽어야 하는 이유다.

테스트는 어디서 무너지나

추상적인 분류보다 실제 장면이 빠르다. 논문이 든 두 예시가 선명하다.

포커 핸드를 판정하는 코드가 있다. 어떤 테스트는 '에이스-로우 스트레이트'와 '트리플'을 맞붙이는 경우를 한 번도 넣지 않았다. 그 비교가 빠지니, 에이스-로우 스트레이트를 하이카드로 잘못 분류하는 틀린 구현이 버젓이 통과한다. 테스트가 그 동작을 아예 거치지 않은 것이다. 이걸 커버리지 공백이라 부른다. 커버리지란 테스트가 관련 코드 줄을 실제로 거쳤는지를 말한다.

괄호 파서(괄호 짝이 맞는지 해석하는 코드) 테스트는 결이 다르다. 짝이 안 맞는 괄호를 입력으로 넣긴 했다. 그런데 결과의 '개수'만 확인했다. 그러다 보니 유효한 괄호 그룹을 통째로 버리는 구현도 '개수는 맞으니 통과'로 받아 준다. 코드에 도달은 했는데 판정을 틀린 것이다. 이걸 잘못된 검사라 부른다. 여기서 '검사'란 테스트가 결과를 맞다/틀리다 가려내는 그 한 줄을 말한다.

두 테스트 모두 고치기 전 코드에선 실패하고 정답 구현에선 통과한다. 기존 방식은 멀쩡히 초록불을 켠다. 패널을 붙이는 순간에야 결함이 드러난다.

이제 숫자를 붙이면 뜻이 또렷해진다. 실패한 테스트 286건을 전문가가 분류했더니, 방금 본 '잘못된 검사'가 50.0%로 가장 컸다. '커버리지 공백'이 34.6%, 테스트 자신의 버그가 15.4%였다. 세 번째는 이런 식이다. 테스트 안에 함수를 넣어 두고 꺼내 쓰다가 인자가 하나 더 딸려 들어가, 정답 구현에서조차 테스트가 깨진다.

여기서 챙길 지점. 실패의 절반은 코드에 도달은 했는데 판정을 틀린 경우다. 커버리지는 '관련 코드에 도달했는가'를 볼 뿐, '도달해서 제대로 판정했는가'까지 보장하지 않는다. 커버리지 숫자만 보고 완료를 찍으면 위험한 이유가 여기 있다.

더 똑똑하게 만드는 법: 서로 맞대어 고치기

재는 잣대를 세웠으니, 점수를 끌어올리는 쪽도 보자. 연구진이 내놓은 생성·검증 절차의 핵심은 한 문장으로 준다. 테스트를 한 번 만들고 끝내지 말고, 서로 다르게 만든 것끼리 맞대어 고쳐라. 세 부분으로 돌아간다.

첫째, 서로 다른 탐색 전략을 받은 에이전트 여러 개가 각자 테스트를 만든다. 전략을 일부러 다르게 주는 건, 실패하는 길이 하나가 아니기 때문이다. 다른 각도로 찔러야 다른 결함을 건드린다.

둘째, 한 에이전트가 만든 테스트를 다른 에이전트가 고친 코드에 돌려 본다. 내 테스트가 남의 멀쩡한 코드를 틀렸다고 하면, 그 테스트가 특정 구현에 과하게 묶여 있다는 신호다. 그 반례로 테스트를 고친다. 여기에 더해, 요구사항만 본 독립 리뷰어가 판정이 맞는지 따로 점검한다.

셋째, 이렇게 다듬은 '생성 전략' 자체를 훈련 과제로 한 번 더 손본 뒤, 그 전략을 얼려서 본 시험에 투입한다.

가장 흥미로운 결과는 부품을 하나씩 떼보며 기여도를 재는 실험(ablation)에서 나온다. 테스트를 1벌에서 3벌로 늘리기만 하면 점수는 거의 움직이지 않았다. 그런데 그 3벌을 서로 맞대어 고치는 교차 검증을 켜자 점수가 확 뛰었다. 두 경우 모두 과제당 쓸 수 있는 계산량(추론 예산)은 똑같이 묶어 둔 상태였다. 즉 이득은 '더 많이 생성'이 아니라 '서로 검증'에서 나왔다. 완성형 절차는 모델이 기본으로 쓰던 방식보다 8.67~9.00%포인트, 가장 단순한 한 벌짜리 구성보다 11.67~12.00%포인트 높았다. 기준이 다른 두 숫자이니 합쳐 읽으면 안 된다.

솔직한 타협도 하나 있다. 전략을 다듬을 때 정작 간판 지표인 JSF가 아니라 옛 지표 RAR을 목표로 삼았다. JSF가 너무 빡빡해 학습 신호가 거의 안 뜨기 때문이다. 재는 목표와 올리는 목표가 어긋나 있다는 건 연구진도 인정한 대목이다.

온틀 QA에 적용한다면

이 연구가 남의 일로 안 읽히는 건, 온틀이 일하는 방식이 정확히 이 그림이라서다.

장면 하나를 끝까지 따라가 보자. 한 고객 프로젝트에서 AI PM이 결제 환불 계산 기능을 맡았다. AI가 코드를 짜고, 같은 흐름에서 그 코드를 검증할 테스트를 만들고, 테스트가 초록불이 되자 "환불 계산 완료"로 고객에게 보고한다. 이 초록불이 바로 정답 하나짜리 채점이다. 방금 본 대로, 그 품질은 체계적으로 부풀려져 있을 수 있다. 고객에게 건넨 '완료' 배지가, 실은 똑같이 올바른 다른 구현을 깨뜨리거나 미묘하게 틀린 구현을 눈감아 주는 테스트일 수 있다는 뜻이다.

그래서 바꿀 지점은 온틀 파이프라인의 검토·QA 게이트다. 완료를 찍기 전에 작은 절차를 하나 끼운다.

  1. 완료 직전, 기능을 짠 본체와는 다른 모델로 '올바른 대안 구현 두세 개'와 '의도적으로 망가뜨린 구현 두세 개'를 만든다. 온틀은 이미 단계마다 다른 모델을 쓰는 라우팅이 있으니, 대안 생성을 본체와 다른 모델에 맡기는 건 추가 설비 없이도 된다.
  2. 생성한 테스트를 그 패널 전체에 돌린다. 대안 구현을 모두 통과시키고, 망가뜨린 구현을 모두 걸러야 통과로 친다.
  3. 커버리지가 높다고 끝내지 않는다. 엣지 입력을 넣어 판정이 결함을 실제로 잡는지 확인한다.
  4. 무엇이 올바르고 무엇이 틀렸는지(패널의 라벨)는, 요구사항만 본 독립 검토가 확정한다. 사람이 하든 별도 에이전트가 하든. 이 라벨이 허술하면 게이트도 허술하다.
  5. 전 과제에 걸지 않는다. 틀린 '완료'의 대가가 큰 곳, 돈이 오가는 계산이나 데이터를 바꾸는 기능부터 좁게 건다.

성급한 등치는 경계해야 한다. 이 연구가 다룬 건 '에이전트가 생성한 테스트'의 품질을 재는 문제다. 온틀의 모든 완료 판정이 이 구조와 똑같지는 않다. 사람이 손으로 짠 테스트나 고객이 이미 가진 테스트까지 같은 숫자로 묶어 말할 근거는 없다. 비용도 공짜가 아니다. 구현을 하나 더 세울 때마다 테스트를 처음부터 다시 돌려야 한다. 그래서 패널은 작게, 게이트는 좁게 가는 게 맞다. 고객 화면엔 여전히 '완료'만 보이더라도, 그 뒤에서 게이트가 정답 하나가 아니라 여러 구현으로 검증을 끝냈는지가 신뢰의 실체가 된다.

자주 묻는 질문

그럼 테스트 커버리지 도구는 이제 쓸모없다는 건가요?

아니다. 커버리지는 JSF와 양(+)의 상관을 보였다. 관련 코드에 도달하는 건 분명 도움이 된다. 다만 '도달'과 '올바른 판정'은 다르다. 커버리지는 필요조건이지 충분조건이 아니라는 뜻이다. 커버리지를 버리지 말고, 그 위에 '도달한 뒤 판정이 맞는가'를 재는 한 겹을 더 얹는 쪽이 맞다.

틀린 구현을 변이로 안 만들고 실제 오답으로 모으면 뭐가 낫고 뭐가 아쉽나요?

장점은 현실성이다. 사람이나 모델이 실제로 저지르는 실수에 가까운 결함 분포를 얻는다. 아쉬운 점은 그 분포가 결국 오답을 만든 모델들의 실수 성향을 따라간다는 것이다. 그들이 잘 안 내는 종류의 결함은 패널에 덜 들어온다. 여러 공개 벤치마크에서 과제를 끌어온 만큼 그 원본들의 편향도 일부 물려받는다.

작은 팀이라 대안 구현을 여럿 만들 여력이 없으면 어디부터 시작하나요?

가장 자주 고장 나는 기능 하나부터 고르면 된다. 새 구현을 만들 필요도 없이, 지난 버그 리포트에서 실제로 틀렸던 코드를 '무효 구현'으로 재활용하고, 과거의 올바른 리팩토링본을 '유효 구현'으로 쓰는 것부터가 출발점이다. 패널은 두세 개면 충분하다. 핵심은 규모가 아니라, 정답 하나를 벗어난 검증을 한 번이라도 돌려 보는 것이다.

테스트가 통과해도 못 믿으면, 그럼 뭘 믿고 배포하나요?

완벽한 보증은 어차피 없다. 패널은 '절대 안전'을 주는 게 아니라, 초록불의 근거를 정답 하나에서 여럿으로 넓혀 주는 장치다. 신뢰를 0 또는 1로 보지 말고, 검증의 폭을 위험도에 맞춰 조절하는 다이얼로 보는 편이 실무에 맞는다. 돈이 걸린 기능엔 패널을 넓게, 가벼운 변경엔 좁게.

참고자료