트웰브랩스

Spec-Driven Test Automation: AI는 왜 늘 적당히 테스트 코드를 작성할까?

Luke Cha

AI에게 개발과 테스트를 함께 맡기면 늘 "적당한" 수준의 테스트만 나오는데, 원인은 검증 기준이 될 Spec의 부재였습니다.

AI에게 개발과 테스트를 함께 맡기면 늘 "적당한" 수준의 테스트만 나오는데, 원인은 검증 기준이 될 Spec의 부재였습니다.

In this article

No headings found on page

Join our newsletter

Receive the latest advancements, tutorials, and industry insights in video understanding

AI로 영상을 검색하고, 분석하고, 탐색하세요.

2026. 7. 23.

8 mins

링크 복사하기

세 줄 요약

  • AI에게 개발부터 테스트까지 한 번에 맡겼더니, 늘 "적당한" 수준의 테스트 코드만 만들었습니다. Rule을 넣어도 모델을 바꿔도 그대로였습니다.

  • 문제의 원인은 테스트의 기준이 되는 Spec의 부재였습니다. 그래서 다양한 개발 문서(Design Document, API Specification 등)에서 Spec.md를 추출·정리하는 Agent(spec-writer)와, 그 기준대로만 테스트를 작성하는 Agent(test-writer)를 도입했습니다. 단, 작성된 Spec은 사람이 리뷰해 확정합니다.

  • 이를 통해 "적당한" 수준이 아니라 높은 커버리지를 만족하는 테스트 케이스가 작성되었고, 그동안 놓치고 있던 결함까지 발견할 수 있었습니다.

문제 — AI가 짜준 테스트가 얕다

Claude Code 같은 Agentic AI 도구를 한 번이라도 써봤다면, 명령 한 줄로 간단한 게임이나 웹페이지가 뚝딱 만들어지는 순간을 경험해 봤을 겁니다. 그 맛에 자신감이 붙으면 자연스럽게 욕심이 생깁니다. "그럼 우리 프로젝트도 개발부터 테스트까지 다 맡겨볼까?"

문제는 그다음입니다. Agent가 짜준 테스트 스크립트는 처음 볼 땐 그럴듯합니다. 그런데 코드를 조금만 들여다보면 묘한 위화감이 듭니다. 호출은 분명히 하는데, 정작 검증이 얕습니다. 예를 들어 인덱스를 만드는 엔드포인트를 테스트한다며 내놓는 건 보통 이런 모양입니다.

def test_create_index():
    resp = client.post("/indexes", json={"name": "demo", "model": "marengo"})
    assert resp.status_code == 201
    assert resp.json()["id"]
def test_create_index():
    resp = client.post("/indexes", json={"name": "demo", "model": "marengo"})
    assert resp.status_code == 201
    assert resp.json()["id"]

정상 요청을 한 번 보내 201이 떨어지고 응답에 id가 들어 있는가? — 딱 여기까지입니다. 눈에 보이는 happy case 하나만 짚고 끝납니다. 응답 본문의 나머지 필드(status, created_at 등)가 Spec과 맞는지, 만들어진 인덱스가 ready 상태로 제대로 넘어가는지, ready가 된 index가 정상적으로 검색되는지 등은 전혀 손대지 않습니다. 즉 "통과하는 데 필요한 만큼만" 하고 멈춥니다.

시도 — Rule을 더해도, 모델을 바꿔도 제자리

처음엔 흔한 방법들을 시도했습니다. 검증이 얕다고 짚어주면 그 자리에서는 곧잘 고칩니다. "assertion은 구체적으로 작성하라"는 규칙을 프롬프트에 잔뜩 넣어보기도 했습니다.

둘 다 그때뿐이었습니다. 지적한 부분은 메우는데, 정작 중요한 흐름은 여전히 놓치고 지나갑니다. 룰을 아무리 더해도 정작 중요한 핵심 로직은 점검하지 않고 수행이 완료됩니다.

더 좋은 모델로 바꾸면 나아질까요? 아쉽게도 모델을 바꿔도 이 성향은 사라지지 않았습니다. 왜 AI는 스스로 엣지 케이스를 찾지 못하고, 사람이 짚어줄 때만 제대로 고치는 걸까요? 어쩌면 모델의 능력이 부족한 게 아니라, 일을 시키는 '방식' 자체가 빠져 있는 건 아닐까요?

빠져 있던 것 — Spec!

단서는 앞의 실패 안에 있었습니다.

왜 잘못된 부분을 확인하고, 그걸 고치라고 할 때만 제대로 수행할까요?

Agent가 제대로 방향을 잡은 건, 우리가 "이게 맞다"고 짚어줬을 때뿐이었습니다. 바꿔 말하면, 중요한 흐름이 무엇인지를 판단하는 일은 애초에 사람의 몫인 것 같습니다.

Agent가 잘할 수 있는 일은 명확합니다. 주어진 기준대로 수행하는 거죠. 대신 주어진 기준이 불명확할 경우, Agent는 명확한 부분까지만 수행하게 됩니다. 그래서 이 기준을 제대로 줄 수 있는 Spec이 중요합니다.

Spec이 모호하다면 Agent는 결국 "코드가 지금 하는 그대로"를 정답으로 베껴 적습니다. 그건 검증이 아니라 동어반복이고, 구현이 틀려도 테스트는 얌전히 통과합니다.

검증의 정답은 코드 안에서 나오지 않는다. 사람이 바깥에서 고정해 줘야 한다.

그럼 그 기준은 어디서 가져올까요? 테스트 엔지니어에겐 아주 기초적이면서 중요한 이론이 있습니다. 바로 명세 기반 테스트 설계(Specification-based Test Design) 기법이죠. 쉽게 말하면 명세(Spec)를 기준으로 테스트 케이스를 도출하고 수행한다는 겁니다. Agent에게도 이 방법을 적용합니다. 그럼 Spec은 어떤 것들을 확인해야 할까요? Spec의 출처를 개발 문서와 산출된 OpenAPI 문서로 고정했습니다. 그리고 개발 코드는 참고로만 쓰게 했습니다 — Spec이 애매할 때 "실제로는 이렇게 동작하는구나"를 확인하는 보조 수단일 뿐이죠. 코드를 정답으로 끌어다 쓰는 순간 다시 동어반복으로 굴러떨어지니까요.

문제의 해결 — Agent를 둘로 나누기

문제를 알았으니, 문제가 반복되지 않게 Agent를 둘로 나눴습니다. 개발 문서를 통해 테스트의 기준이 될 Spec을 정의하고 작성하는 일과, 그 기준대로 테스트를 쓰는 일을 같은 Agent에게 맡기지 않기로 했습니다.

Agent 1: Spec-writer — Spec을 도출하고 고정한다

Spec 도출에 필요한 입력 자료가 중요합니다. 명세 기반 설계 기법의 가장 핵심은 설계에 필요한 개발 문서입니다. End-to-End API 테스트를 작성한다고 했을 때, Test Engineer로서 필수적으로 확인하는 건 새롭게 개발되는 기능의 Technical Design Document와 API Spec이 담긴 OpenAPI(Swagger) 문서입니다. 제3자 입장에서 테스트를 진행할 때는 이 문서로 테스트 케이스를 도출하고, 문서가 모호하거나 API Spec에 필수 정보가 없을 때는 개발자에게 되묻고, 이해가 가지 않는 부분은 개발자와 소통해서 빠짐없는 테스트 케이스(Test case)를 도출하는 걸 목표로 합니다.

Spec-writer에도 이 기준을 적용했습니다. Spec-writer는 Technical Design Document와 API Spec만을 주된 참고 자료로 삼고, 개발자의 코드는 우선순위를 낮춰 앞선 문서에서 구체화되지 않은 정보를 확인하는 참고 용도로만 쓰도록 했습니다.

Spec-writer는 테스트 케이스 도출에 필요한 문서를 만들어 tests/feature/spec-{feature}.md로 저장하게 했습니다.

그리고 두 가지를 강제했습니다. 작성된 Spec은 지시자(사용자)가 필수 비즈니스 흐름을 반드시 리뷰해 확정하고, 중요한 흐름은 Spec 안에 필수 항목으로 못 박습니다. 정리하면 순서는 늘 같습니다 — 사용자에게 노출되는 Spec을 고정 → 지시자의 확인 → 그 기준 위에서 강화된 테스트 작성.

리뷰 절차를 넣은 이유: 테스트가 엉뚱해지는 대부분의 원인이 Agent가 사용자 의도 밖으로 흘러가는 데 있었고, 특히 같은 코드를 계속 고치다 보면 어느 순간 Agent가 제멋대로 테스트를 손보기 시작하기 때문입니다. 그래서 Spec을 항상 기준 문서로 남겨두면, 의도하지 않은 변경이 끼어들었을 때 이를 감지하는 안전장치가 됩니다. 테스트가 통과를 위해 슬쩍 흐름을 바꿔도, 고정된 Spec과 대조하면 그 이탈이 드러나니까요.

그래서 Spec은 Agent가 자동으로 고치지 못하게 막았습니다. 흐름이 바뀐 낌새가 잡히면 Agent는 그 사실을 드러내고 멈출 뿐, 수정 여부는 사람이 정합니다.

Agent 2: Test-writer — 기준대로만 작성한다

두 번째 Agent는 고정된 Spec을 보고 테스트를 어떻게 작성할지 안내합니다. Spec에서 필수 흐름으로 지정된 곳은 강화된 형태로 반드시 테스트하고, 일반 흐름도 적당히가 아니라 구체화된 수준으로 작성하도록요. 추가적으로 다양한 테스트 노하우도 함께 넣었습니다.

달라진 핵심은 Test-writer가 더 이상 구현 코드를 바라보지 않고, 사람이 리뷰해 고정한 Spec을 바라본다는 점입니다. 또한 종료 조건이 "통과"에서 "Spec 충족"으로 바뀌면서, 앞에서 그렇게 안 고쳐지던 "적당히 하고 멈추는" 버릇이 비로소 줄었습니다.

다음은 Test-writer가 새롭게 작성한 테스트 코드입니다. Marengo의 필수 기능 중 하나인 Index 생성에 대해 검증을 강화한 예시입니다. Spec에 적힌 모든 검증을 빠짐없이 채웠고, 오류·경계 케이스와 명시된 상태 전이 케이스를 제대로 작성하였습니다.

def test_create_index_returns_full_contract():
    resp = client.post("/indexes", json={"name": "demo", "model": "marengo"})
    assert resp.status_code == 201
    body = resp.json()
    # Spec에 선언된 응답 스키마를 전부 검증
    assert is_uuid(body["id"])
    assert body["name"] == "demo"
    assert body["model"] == "marengo"
    assert body["status"] == "creating"
    assert body["created_at"]

@pytest.mark.parametrize("payload, status, detail", [
    ({}, 422, "name is required"),
    ({"name": "demo"}, 422, "model is required"),
    ({"name": "x" * 256, "model": "marengo"}, 400, "name too long"),
    ({"name": "demo", "model": "unknown"}, 400, "unsupported model"),
])
def test_create_index_rejects_invalid_input(payload, status, detail):
    # Spec의 validation 표를 그대로 테스트로 옮긴다
    resp = client.post("/indexes", json=payload)
    assert resp.status_code == status
    assert detail in resp.json()["detail"]

def test_create_index_duplicate_name_conflicts():
    client.post("/indexes", json={"name": "dup", "model": "marengo"})
    resp = client.post("/indexes", json={"name": "dup", "model": "marengo"})
    assert resp.status_code == 409  # Spec에 정의된 같은 이름 재생성은 충돌

def test_index_reaches_ready_state():
    idx = client.post("/indexes", json={"name": "demo", "model": "marengo"}).json()
    # Spec에 정의된 상태 전이 케이스
    final = wait_for_status(idx["id"], target="ready", timeout=30)
    assert final["status"] == "ready"
def test_create_index_returns_full_contract():
    resp = client.post("/indexes", json={"name": "demo", "model": "marengo"})
    assert resp.status_code == 201
    body = resp.json()
    # Spec에 선언된 응답 스키마를 전부 검증
    assert is_uuid(body["id"])
    assert body["name"] == "demo"
    assert body["model"] == "marengo"
    assert body["status"] == "creating"
    assert body["created_at"]

@pytest.mark.parametrize("payload, status, detail", [
    ({}, 422, "name is required"),
    ({"name": "demo"}, 422, "model is required"),
    ({"name": "x" * 256, "model": "marengo"}, 400, "name too long"),
    ({"name": "demo", "model": "unknown"}, 400, "unsupported model"),
])
def test_create_index_rejects_invalid_input(payload, status, detail):
    # Spec의 validation 표를 그대로 테스트로 옮긴다
    resp = client.post("/indexes", json=payload)
    assert resp.status_code == status
    assert detail in resp.json()["detail"]

def test_create_index_duplicate_name_conflicts():
    client.post("/indexes", json={"name": "dup", "model": "marengo"})
    resp = client.post("/indexes", json={"name": "dup", "model": "marengo"})
    assert resp.status_code == 409  # Spec에 정의된 같은 이름 재생성은 충돌

def test_index_reaches_ready_state():
    idx = client.post("/indexes", json={"name": "demo", "model": "marengo"}).json()
    # Spec에 정의된 상태 전이 케이스
    final = wait_for_status(idx["id"], target="ready", timeout=30)
    assert final["status"] == "ready"

같은 엔드포인트인데, 검증 범위가 이렇게 갈립니다.

검증 항목                              Before     Spec 기반 테스트
──────────────────────────────────────────────────────────────────
성공 응답(201) + id 존재                   O              O
응답 스키마 전체 필드                        X              O
필수 필드 누락 422                       X              O
허용되지 않는 400                      X              O
같은 이름 재생성 409                      X              O
상태 전이(creating  ready)               X              O
──────────────────────────────────────────────────────────────────
커버 항목                                1/6            6/6
검증 항목                              Before     Spec 기반 테스트
──────────────────────────────────────────────────────────────────
성공 응답(201) + id 존재                   O              O
응답 스키마 전체 필드                        X              O
필수 필드 누락 422                       X              O
허용되지 않는 400                      X              O
같은 이름 재생성 409                      X              O
상태 전이(creating  ready)               X              O
──────────────────────────────────────────────────────────────────
커버 항목                                1/6            6/6

세 가지가 달라졌습니다. 첫째, Spec에 적힌 모든 validation을 빠짐없이 검증합니다 — 필수 필드, 길이 제한, 허용되지 않는 값까지 Spec의 표를 그대로 테스트로 옮깁니다. 둘째, 변경이 생기면 바뀐 부분을 콕 집어 테스트합니다. 셋째, 논리적 상태가 있으면 모든 상태를 끝까지 따라갑니다 — 만들어진 인덱스가 creating에서 ready로 실제로 넘어가는지까지 확인합니다.

효과는 곧 드러났습니다. happy case 한 줄짜리였던 테스트가 검증 항목 여러 개로 늘면서, 그동안 조용히 통과하던 결함들이 잡히기 시작했습니다. 응답 스키마에서 빠져 있던 created_at, 일부 설정에서 상태가 ready로 넘어가지 않고 creating에 멈춰 있던 문제 — 전부 happy case만 보던 예전 테스트라면 끝까지 못 봤을 것들입니다.

두 Agent에 실제로 적어둔 규칙

프로젝트마다 경로나 도구는 다르겠지만, 두 .md의 뼈대가 되는 규칙은 어디서나 그대로 쓸 수 있습니다. 핵심만 옮기면 이렇습니다.

Spec-writer의 핵심 규칙

- 결과물: 도출한 Spec은 tests/feature/spec-{feature}.md 파일로 저장한다.
- 단일 책임: Spec을 기준 계약(개발 문서·API 정의 )동기화하는 일만 한다.  외에는 손대지 않는다.
- 기준 계약 자체는 절대 조용히 고치지 않는다. 충돌이 보이면 해결하지 말고 드러낸다.
- Spec의 정답은 개발 문서와 API 계약에서 가져온다. 구현 코드는 문서가 구체화하지 않은 상수나 동작을 확인하는 보조 수단일 , "올바름"정의로 쓰지 않는다. 코드를 그대로 옮겨 적는 순간 검증은 동어반복이 된다.
- 항목이 추가되면 Spec에 반영하고 "이 흐름에는 테스트가 필요하다"후속 제안만 남긴다.
- 이상 쓰이지 않는 항목이라도 기존 테스트가 그걸 참조하고 있으면 바로 지우지 않는다. 별도 Drift 항목으로 남기고, 삭제 여부는 사람에게 확인받는다.
- Spec과 구현이 의미상 충돌하면(: 문서는 A, 구현은 B) 작업을 멈추고, 선택지(구현 수정 / Spec 수정 / 보류)제시한 사람의 확인을 기다린다.
- 상태를 가진 리소스는 상태 전이 매트릭스를 그려, 모든 전이가 Spec의 흐름과 하나씩 매핑되는지 점검한다. 매핑되지 않는 전이는 누락으로 드러내고 흐름으로 보충한다.
- 절대 하지 않는 : 기준 계약 수정, 테스트 코드 수정, 비즈니스 흐름 설명을 임의로 바꾸기.
- (기타 개발  API문서를 해석하는 세부 기준 

- 결과물: 도출한 Spec은 tests/feature/spec-{feature}.md 파일로 저장한다.
- 단일 책임: Spec을 기준 계약(개발 문서·API 정의 )동기화하는 일만 한다.  외에는 손대지 않는다.
- 기준 계약 자체는 절대 조용히 고치지 않는다. 충돌이 보이면 해결하지 말고 드러낸다.
- Spec의 정답은 개발 문서와 API 계약에서 가져온다. 구현 코드는 문서가 구체화하지 않은 상수나 동작을 확인하는 보조 수단일 , "올바름"정의로 쓰지 않는다. 코드를 그대로 옮겨 적는 순간 검증은 동어반복이 된다.
- 항목이 추가되면 Spec에 반영하고 "이 흐름에는 테스트가 필요하다"후속 제안만 남긴다.
- 이상 쓰이지 않는 항목이라도 기존 테스트가 그걸 참조하고 있으면 바로 지우지 않는다. 별도 Drift 항목으로 남기고, 삭제 여부는 사람에게 확인받는다.
- Spec과 구현이 의미상 충돌하면(: 문서는 A, 구현은 B) 작업을 멈추고, 선택지(구현 수정 / Spec 수정 / 보류)제시한 사람의 확인을 기다린다.
- 상태를 가진 리소스는 상태 전이 매트릭스를 그려, 모든 전이가 Spec의 흐름과 하나씩 매핑되는지 점검한다. 매핑되지 않는 전이는 누락으로 드러내고 흐름으로 보충한다.
- 절대 하지 않는 : 기준 계약 수정, 테스트 코드 수정, 비즈니스 흐름 설명을 임의로 바꾸기.
- (기타 개발  API문서를 해석하는 세부 기준 

Test-writer의 핵심 규칙

- spec-{feature}.md의 내용만을 기준으로 테스트 케이스를 작성한다. 구현 코드, 기준 계약, 공용 라이브러리, 다른 Agent 정의는 건드리지 않는다.
- 가지 모드로만 동작한다. 작성(Spec에 선언됐는데 아직 없는 테스트를 채움) 정렬(실제 계약과 어긋난 기존 테스트를 맞춤).
- 종료 조건은 "통과"아니라 "Spec 충족"이다. Spec이 선언한 흐름과 검증 항목을 선언된 강도 그대로 채웠을 때만 끝난 것으로 본다.
- 작성할 대상 = "Spec이 선언한 테스트" "이미 존재하는 테스트".  차집합만 채운다.
- Spec에서 필수 흐름으로 지정된 곳은 강화된 형태로 반드시 검증하고, 일반 흐름도 적당히가 아니라 구체화된 수준으로 작성한다.
- happy case도 최소한으로 멈추지 않는다. Spec에 선언된 응답 스키마의 모든 필드를 검증하고, 상태 전이가 선언돼 있으면 마지막 상태까지 따라가 확인한다.
- 통과시키려고 단언(assertion)약화시키지 않는다. 구현이 진짜 잘못된 같으면 테스트를 고치지 말고 우려 사항으로 올려 사람의 판단을 받는다.
- 번에 하나(작성은  묶음, 정렬은 테스트) 다룬다. 판단이 필요한 변경을 한꺼번에 펼치지 않는다.
- 기존 테스트의 스타일을 따른다. 새로 쓰기 전에 이웃 테스트를 먼저 읽는다.
- (기타 pytest 스크립트 작성 가이드 구체적인 테스트 스크립트 작성 기준 

- spec-{feature}.md의 내용만을 기준으로 테스트 케이스를 작성한다. 구현 코드, 기준 계약, 공용 라이브러리, 다른 Agent 정의는 건드리지 않는다.
- 가지 모드로만 동작한다. 작성(Spec에 선언됐는데 아직 없는 테스트를 채움) 정렬(실제 계약과 어긋난 기존 테스트를 맞춤).
- 종료 조건은 "통과"아니라 "Spec 충족"이다. Spec이 선언한 흐름과 검증 항목을 선언된 강도 그대로 채웠을 때만 끝난 것으로 본다.
- 작성할 대상 = "Spec이 선언한 테스트" "이미 존재하는 테스트".  차집합만 채운다.
- Spec에서 필수 흐름으로 지정된 곳은 강화된 형태로 반드시 검증하고, 일반 흐름도 적당히가 아니라 구체화된 수준으로 작성한다.
- happy case도 최소한으로 멈추지 않는다. Spec에 선언된 응답 스키마의 모든 필드를 검증하고, 상태 전이가 선언돼 있으면 마지막 상태까지 따라가 확인한다.
- 통과시키려고 단언(assertion)약화시키지 않는다. 구현이 진짜 잘못된 같으면 테스트를 고치지 말고 우려 사항으로 올려 사람의 판단을 받는다.
- 번에 하나(작성은  묶음, 정렬은 테스트) 다룬다. 판단이 필요한 변경을 한꺼번에 펼치지 않는다.
- 기존 테스트의 스타일을 따른다. 새로 쓰기 전에 이웃 테스트를 먼저 읽는다.
- (기타 pytest 스크립트 작성 가이드 구체적인 테스트 스크립트 작성 기준 

명확하게 역할을 구분 짓고, 애매하게 정의되는 부분을 없애고, 최종 결과(구체화된 테스트 케이스 작성)까지 도달하도록 하였습니다. 물론 사람이 개입되어야 할 부분도 정확하게 명시했고요.

실제로 돌고 있는 코드에서 — 검증된 것만

바뀐 방식으로 테스트 케이스 작성을 요청하였습니다. 개발 문서를 통해 작성된 Spec은 개발자의 리뷰를 거치게 되어 더 이상 모호한 기준으로 테스트 케이스가 작성되지 않게 되었습니다. 그리고 Test-writer의 세부적인 테스트 케이스 작성 기준은 테스트 케이스를 더욱 정확하고 세부적으로 만들었습니다.

발견된 버그 — Spec과 구현의 어긋남

작성된 케이스에서는 one-shot 방식으로는 찾기 어려운 사례도 도출되었습니다. 이번에는 위의 인덱스 생성과는 다른 API — 파일을 비동기로 처리하는 작업(Task)의 상태 조회 엔드포인트 — 에서 나온 사례입니다.

# Task 상태 조회 API — Spec 문서 기준: 완료 상태는 "SUCCESS"
assert body.get("status") == "SUCCESS", body

# ============ FAILED ============
# AssertionError: {"status": "completed", ...}
# 문서는 SUCCESS, 구현은 completed — Spec과 구현이 어긋나 있었다
# Task 상태 조회 API — Spec 문서 기준: 완료 상태는 "SUCCESS"
assert body.get("status") == "SUCCESS", body

# ============ FAILED ============
# AssertionError: {"status": "completed", ...}
# 문서는 SUCCESS, 구현은 completed — Spec과 구현이 어긋나 있었다

AI를 통해 구현된 코드에서는 Status의 완료를 소문자(completed)로 정의하고 있었습니다. AI로 개발될 때 Status 관리가 구체적으로 관리되지 않았거나, 혹은 Context 유실로 이렇게 개발이 된 것 같습니다. 하지만 실제 API Spec 문서에는 완료 상태가 대문자(SUCCESS)로 정의되어 있었습니다. 만약 Client 개발자가 API Spec 문서만 믿고 연동을 구현했다면, 영원히 '완료' 상태를 인지하지 못하고 timeout 실패로 처리되는 버그가 발생했을 것입니다.

그래서 적당히 status code 200만 점검했다면, 아마 버그 리포트가 올라오기 전까지 발견하지 못했을 수 있습니다. 테스트 코드에서는 정확한 상태를 확인하는 검증 단계가 꼭 필요합니다. 이건 Test-writer의 강화된 기준을 통해 달성할 수 있었습니다. 이 어긋남은 리뷰를 거쳐 Spec의 Drift 항목으로 기록되었고, 상태 어휘를 통일하는 것으로 정리되었습니다.

개선된 수치 — 양보다 질

다음 표의 Before는 Agent에게 처음부터 끝까지 다 맡겼을 때의 Test Suite이고, After는 새로운 기준으로 재구축한 결과입니다. 핵심은 개수가 늘어난 게 아니라, 테스트의 구조와 커버리지의 질이 개선되었다는 점입니다.

                    Before(평면)              After(spec-driven)
────────────────────────────────────────────────────────────────────
테스트 파일          18 (폴더)             12기능 그룹으로 분리
기준 문서(spec.md)   없음                      그룹마다 1
테스트 함수          248*                    151 (spec과 1:1, 중복 없음)
공통 코드           파일마다 복붙               lib/ 공용 모듈로 통합
전체 코드량          7,6345,669 (−26%)
────────────────────────────────────────────────────────────────────
* 이미 삭제된 기능·내부 서비스 대상 테스트 31개는 제외한 비교
                    Before(평면)              After(spec-driven)
────────────────────────────────────────────────────────────────────
테스트 파일          18 (폴더)             12기능 그룹으로 분리
기준 문서(spec.md)   없음                      그룹마다 1
테스트 함수          248*                    151 (spec과 1:1, 중복 없음)
공통 코드           파일마다 복붙               lib/ 공용 모듈로 통합
전체 코드량          7,6345,669 (−26%)
────────────────────────────────────────────────────────────────────
* 이미 삭제된 기능·내부 서비스 대상 테스트 31개는 제외한 비교
tests/e2e/
├── conftest.py          # 공용 픽스처 (게이트웨이 클라이언트·인증)
├── lib/                 # 공용 헬퍼 (클라이언트·팩토리·폴링)
├── indexes/
│   ├── indexes.spec.md  # ← 기준(정답지)
│   └── test_*.py        # spec을 기준으로 작성된 테스트
├── tasks/
│   ├── tasks.spec.md
│   └── test_*

tests/e2e/
├── conftest.py          # 공용 픽스처 (게이트웨이 클라이언트·인증)
├── lib/                 # 공용 헬퍼 (클라이언트·팩토리·폴링)
├── indexes/
│   ├── indexes.spec.md  # ← 기준(정답지)
│   └── test_*.py        # spec을 기준으로 작성된 테스트
├── tasks/
│   ├── tasks.spec.md
│   └── test_*

개수만 보면 테스트는 오히려 줄었습니다. 하지만 기존 스위트에서는 같은 "생성 → 적재 → 검색" 왕복을 스모크·여정·시나리오라는 이름만 바꿔 반복하는 테스트가 73개였고, 6개는 skip 처리된 채 방치돼 있었습니다.

기준 문서가 없으니 무엇을 커버하는지 셀 방법도 없었죠. 재구축 후에는 모든 테스트가 spec에 선언된 흐름과 1:1로 대응하고, 작성 대상이 "spec이 선언한 테스트 − 이미 존재하는 테스트"라는 차집합으로 정해지기 때문에 중복이 구조적으로 생기지 않습니다. 공통 코드도 정리됐습니다.

파일마다 복사되던 HTTP 클라이언트(39곳)와 폴링 루프(5곳)가 공용 모듈 하나로 모이면서 전체 코드는 26% 줄었고, 테스트별 리소스 격리 덕분에 병렬 실행도 가능해졌습니다.

마치며

저는 이 접근 방식을 Spec-Driven Test Automation이라고 정의했습니다.

Spec을 기반으로 코드를 생성하는 Spec-Driven Development와는 다르게, 여기서는 사람이 확정한 Spec을 정답으로 삼고, 테스트 자동화가 그 Spec을 지속적으로 검증하도록 만드는 방식에 가깝습니다.

즉, AI가 임의로 “적당한 테스트”를 작성하게 하는 것이 아니라, 사람이 고정한 Spec을 기준으로 테스트 범위와 검증 기준이 흔들리지 않도록 유지하는 접근입니다.

완전 자동화는 어렵습니다. 하지만 그건 AI가 모자라서가 아니라, 무엇이 옳은지를 정의하고 책임지는 일이 — 적어도 지금은 — 사람의 몫이기 때문입니다. Agent가 잘하는 것과 사람이 반드시 해야 하는 역할을 구분 짓고, 그 경계를 분명히 하는 순간, 자동화는 비로소 믿을 만해집니다.


팀과 여정을 함께할 분들을 찾고 있습니다 → [TwelveLabs Careers]

세 줄 요약

  • AI에게 개발부터 테스트까지 한 번에 맡겼더니, 늘 "적당한" 수준의 테스트 코드만 만들었습니다. Rule을 넣어도 모델을 바꿔도 그대로였습니다.

  • 문제의 원인은 테스트의 기준이 되는 Spec의 부재였습니다. 그래서 다양한 개발 문서(Design Document, API Specification 등)에서 Spec.md를 추출·정리하는 Agent(spec-writer)와, 그 기준대로만 테스트를 작성하는 Agent(test-writer)를 도입했습니다. 단, 작성된 Spec은 사람이 리뷰해 확정합니다.

  • 이를 통해 "적당한" 수준이 아니라 높은 커버리지를 만족하는 테스트 케이스가 작성되었고, 그동안 놓치고 있던 결함까지 발견할 수 있었습니다.

문제 — AI가 짜준 테스트가 얕다

Claude Code 같은 Agentic AI 도구를 한 번이라도 써봤다면, 명령 한 줄로 간단한 게임이나 웹페이지가 뚝딱 만들어지는 순간을 경험해 봤을 겁니다. 그 맛에 자신감이 붙으면 자연스럽게 욕심이 생깁니다. "그럼 우리 프로젝트도 개발부터 테스트까지 다 맡겨볼까?"

문제는 그다음입니다. Agent가 짜준 테스트 스크립트는 처음 볼 땐 그럴듯합니다. 그런데 코드를 조금만 들여다보면 묘한 위화감이 듭니다. 호출은 분명히 하는데, 정작 검증이 얕습니다. 예를 들어 인덱스를 만드는 엔드포인트를 테스트한다며 내놓는 건 보통 이런 모양입니다.

def test_create_index():
    resp = client.post("/indexes", json={"name": "demo", "model": "marengo"})
    assert resp.status_code == 201
    assert resp.json()["id"]

정상 요청을 한 번 보내 201이 떨어지고 응답에 id가 들어 있는가? — 딱 여기까지입니다. 눈에 보이는 happy case 하나만 짚고 끝납니다. 응답 본문의 나머지 필드(status, created_at 등)가 Spec과 맞는지, 만들어진 인덱스가 ready 상태로 제대로 넘어가는지, ready가 된 index가 정상적으로 검색되는지 등은 전혀 손대지 않습니다. 즉 "통과하는 데 필요한 만큼만" 하고 멈춥니다.

시도 — Rule을 더해도, 모델을 바꿔도 제자리

처음엔 흔한 방법들을 시도했습니다. 검증이 얕다고 짚어주면 그 자리에서는 곧잘 고칩니다. "assertion은 구체적으로 작성하라"는 규칙을 프롬프트에 잔뜩 넣어보기도 했습니다.

둘 다 그때뿐이었습니다. 지적한 부분은 메우는데, 정작 중요한 흐름은 여전히 놓치고 지나갑니다. 룰을 아무리 더해도 정작 중요한 핵심 로직은 점검하지 않고 수행이 완료됩니다.

더 좋은 모델로 바꾸면 나아질까요? 아쉽게도 모델을 바꿔도 이 성향은 사라지지 않았습니다. 왜 AI는 스스로 엣지 케이스를 찾지 못하고, 사람이 짚어줄 때만 제대로 고치는 걸까요? 어쩌면 모델의 능력이 부족한 게 아니라, 일을 시키는 '방식' 자체가 빠져 있는 건 아닐까요?

빠져 있던 것 — Spec!

단서는 앞의 실패 안에 있었습니다.

왜 잘못된 부분을 확인하고, 그걸 고치라고 할 때만 제대로 수행할까요?

Agent가 제대로 방향을 잡은 건, 우리가 "이게 맞다"고 짚어줬을 때뿐이었습니다. 바꿔 말하면, 중요한 흐름이 무엇인지를 판단하는 일은 애초에 사람의 몫인 것 같습니다.

Agent가 잘할 수 있는 일은 명확합니다. 주어진 기준대로 수행하는 거죠. 대신 주어진 기준이 불명확할 경우, Agent는 명확한 부분까지만 수행하게 됩니다. 그래서 이 기준을 제대로 줄 수 있는 Spec이 중요합니다.

Spec이 모호하다면 Agent는 결국 "코드가 지금 하는 그대로"를 정답으로 베껴 적습니다. 그건 검증이 아니라 동어반복이고, 구현이 틀려도 테스트는 얌전히 통과합니다.

검증의 정답은 코드 안에서 나오지 않는다. 사람이 바깥에서 고정해 줘야 한다.

그럼 그 기준은 어디서 가져올까요? 테스트 엔지니어에겐 아주 기초적이면서 중요한 이론이 있습니다. 바로 명세 기반 테스트 설계(Specification-based Test Design) 기법이죠. 쉽게 말하면 명세(Spec)를 기준으로 테스트 케이스를 도출하고 수행한다는 겁니다. Agent에게도 이 방법을 적용합니다. 그럼 Spec은 어떤 것들을 확인해야 할까요? Spec의 출처를 개발 문서와 산출된 OpenAPI 문서로 고정했습니다. 그리고 개발 코드는 참고로만 쓰게 했습니다 — Spec이 애매할 때 "실제로는 이렇게 동작하는구나"를 확인하는 보조 수단일 뿐이죠. 코드를 정답으로 끌어다 쓰는 순간 다시 동어반복으로 굴러떨어지니까요.

문제의 해결 — Agent를 둘로 나누기

문제를 알았으니, 문제가 반복되지 않게 Agent를 둘로 나눴습니다. 개발 문서를 통해 테스트의 기준이 될 Spec을 정의하고 작성하는 일과, 그 기준대로 테스트를 쓰는 일을 같은 Agent에게 맡기지 않기로 했습니다.

Agent 1: Spec-writer — Spec을 도출하고 고정한다

Spec 도출에 필요한 입력 자료가 중요합니다. 명세 기반 설계 기법의 가장 핵심은 설계에 필요한 개발 문서입니다. End-to-End API 테스트를 작성한다고 했을 때, Test Engineer로서 필수적으로 확인하는 건 새롭게 개발되는 기능의 Technical Design Document와 API Spec이 담긴 OpenAPI(Swagger) 문서입니다. 제3자 입장에서 테스트를 진행할 때는 이 문서로 테스트 케이스를 도출하고, 문서가 모호하거나 API Spec에 필수 정보가 없을 때는 개발자에게 되묻고, 이해가 가지 않는 부분은 개발자와 소통해서 빠짐없는 테스트 케이스(Test case)를 도출하는 걸 목표로 합니다.

Spec-writer에도 이 기준을 적용했습니다. Spec-writer는 Technical Design Document와 API Spec만을 주된 참고 자료로 삼고, 개발자의 코드는 우선순위를 낮춰 앞선 문서에서 구체화되지 않은 정보를 확인하는 참고 용도로만 쓰도록 했습니다.

Spec-writer는 테스트 케이스 도출에 필요한 문서를 만들어 tests/feature/spec-{feature}.md로 저장하게 했습니다.

그리고 두 가지를 강제했습니다. 작성된 Spec은 지시자(사용자)가 필수 비즈니스 흐름을 반드시 리뷰해 확정하고, 중요한 흐름은 Spec 안에 필수 항목으로 못 박습니다. 정리하면 순서는 늘 같습니다 — 사용자에게 노출되는 Spec을 고정 → 지시자의 확인 → 그 기준 위에서 강화된 테스트 작성.

리뷰 절차를 넣은 이유: 테스트가 엉뚱해지는 대부분의 원인이 Agent가 사용자 의도 밖으로 흘러가는 데 있었고, 특히 같은 코드를 계속 고치다 보면 어느 순간 Agent가 제멋대로 테스트를 손보기 시작하기 때문입니다. 그래서 Spec을 항상 기준 문서로 남겨두면, 의도하지 않은 변경이 끼어들었을 때 이를 감지하는 안전장치가 됩니다. 테스트가 통과를 위해 슬쩍 흐름을 바꿔도, 고정된 Spec과 대조하면 그 이탈이 드러나니까요.

그래서 Spec은 Agent가 자동으로 고치지 못하게 막았습니다. 흐름이 바뀐 낌새가 잡히면 Agent는 그 사실을 드러내고 멈출 뿐, 수정 여부는 사람이 정합니다.

Agent 2: Test-writer — 기준대로만 작성한다

두 번째 Agent는 고정된 Spec을 보고 테스트를 어떻게 작성할지 안내합니다. Spec에서 필수 흐름으로 지정된 곳은 강화된 형태로 반드시 테스트하고, 일반 흐름도 적당히가 아니라 구체화된 수준으로 작성하도록요. 추가적으로 다양한 테스트 노하우도 함께 넣었습니다.

달라진 핵심은 Test-writer가 더 이상 구현 코드를 바라보지 않고, 사람이 리뷰해 고정한 Spec을 바라본다는 점입니다. 또한 종료 조건이 "통과"에서 "Spec 충족"으로 바뀌면서, 앞에서 그렇게 안 고쳐지던 "적당히 하고 멈추는" 버릇이 비로소 줄었습니다.

다음은 Test-writer가 새롭게 작성한 테스트 코드입니다. Marengo의 필수 기능 중 하나인 Index 생성에 대해 검증을 강화한 예시입니다. Spec에 적힌 모든 검증을 빠짐없이 채웠고, 오류·경계 케이스와 명시된 상태 전이 케이스를 제대로 작성하였습니다.

def test_create_index_returns_full_contract():
    resp = client.post("/indexes", json={"name": "demo", "model": "marengo"})
    assert resp.status_code == 201
    body = resp.json()
    # Spec에 선언된 응답 스키마를 전부 검증
    assert is_uuid(body["id"])
    assert body["name"] == "demo"
    assert body["model"] == "marengo"
    assert body["status"] == "creating"
    assert body["created_at"]

@pytest.mark.parametrize("payload, status, detail", [
    ({}, 422, "name is required"),
    ({"name": "demo"}, 422, "model is required"),
    ({"name": "x" * 256, "model": "marengo"}, 400, "name too long"),
    ({"name": "demo", "model": "unknown"}, 400, "unsupported model"),
])
def test_create_index_rejects_invalid_input(payload, status, detail):
    # Spec의 validation 표를 그대로 테스트로 옮긴다
    resp = client.post("/indexes", json=payload)
    assert resp.status_code == status
    assert detail in resp.json()["detail"]

def test_create_index_duplicate_name_conflicts():
    client.post("/indexes", json={"name": "dup", "model": "marengo"})
    resp = client.post("/indexes", json={"name": "dup", "model": "marengo"})
    assert resp.status_code == 409  # Spec에 정의된 같은 이름 재생성은 충돌

def test_index_reaches_ready_state():
    idx = client.post("/indexes", json={"name": "demo", "model": "marengo"}).json()
    # Spec에 정의된 상태 전이 케이스
    final = wait_for_status(idx["id"], target="ready", timeout=30)
    assert final["status"] == "ready"

같은 엔드포인트인데, 검증 범위가 이렇게 갈립니다.

검증 항목                              Before     Spec 기반 테스트
──────────────────────────────────────────────────────────────────
성공 응답(201) + id 존재                   O              O
응답 스키마 전체 필드                        X              O
필수 필드 누락 422                       X              O
허용되지 않는 400                      X              O
같은 이름 재생성 409                      X              O
상태 전이(creating  ready)               X              O
──────────────────────────────────────────────────────────────────
커버 항목                                1/6            6/6

세 가지가 달라졌습니다. 첫째, Spec에 적힌 모든 validation을 빠짐없이 검증합니다 — 필수 필드, 길이 제한, 허용되지 않는 값까지 Spec의 표를 그대로 테스트로 옮깁니다. 둘째, 변경이 생기면 바뀐 부분을 콕 집어 테스트합니다. 셋째, 논리적 상태가 있으면 모든 상태를 끝까지 따라갑니다 — 만들어진 인덱스가 creating에서 ready로 실제로 넘어가는지까지 확인합니다.

효과는 곧 드러났습니다. happy case 한 줄짜리였던 테스트가 검증 항목 여러 개로 늘면서, 그동안 조용히 통과하던 결함들이 잡히기 시작했습니다. 응답 스키마에서 빠져 있던 created_at, 일부 설정에서 상태가 ready로 넘어가지 않고 creating에 멈춰 있던 문제 — 전부 happy case만 보던 예전 테스트라면 끝까지 못 봤을 것들입니다.

두 Agent에 실제로 적어둔 규칙

프로젝트마다 경로나 도구는 다르겠지만, 두 .md의 뼈대가 되는 규칙은 어디서나 그대로 쓸 수 있습니다. 핵심만 옮기면 이렇습니다.

Spec-writer의 핵심 규칙

- 결과물: 도출한 Spec은 tests/feature/spec-{feature}.md 파일로 저장한다.
- 단일 책임: Spec을 기준 계약(개발 문서·API 정의 )동기화하는 일만 한다.  외에는 손대지 않는다.
- 기준 계약 자체는 절대 조용히 고치지 않는다. 충돌이 보이면 해결하지 말고 드러낸다.
- Spec의 정답은 개발 문서와 API 계약에서 가져온다. 구현 코드는 문서가 구체화하지 않은 상수나 동작을 확인하는 보조 수단일 , "올바름"정의로 쓰지 않는다. 코드를 그대로 옮겨 적는 순간 검증은 동어반복이 된다.
- 항목이 추가되면 Spec에 반영하고 "이 흐름에는 테스트가 필요하다"후속 제안만 남긴다.
- 이상 쓰이지 않는 항목이라도 기존 테스트가 그걸 참조하고 있으면 바로 지우지 않는다. 별도 Drift 항목으로 남기고, 삭제 여부는 사람에게 확인받는다.
- Spec과 구현이 의미상 충돌하면(: 문서는 A, 구현은 B) 작업을 멈추고, 선택지(구현 수정 / Spec 수정 / 보류)제시한 사람의 확인을 기다린다.
- 상태를 가진 리소스는 상태 전이 매트릭스를 그려, 모든 전이가 Spec의 흐름과 하나씩 매핑되는지 점검한다. 매핑되지 않는 전이는 누락으로 드러내고 흐름으로 보충한다.
- 절대 하지 않는 : 기준 계약 수정, 테스트 코드 수정, 비즈니스 흐름 설명을 임의로 바꾸기.
- (기타 개발  API문서를 해석하는 세부 기준 

Test-writer의 핵심 규칙

- spec-{feature}.md의 내용만을 기준으로 테스트 케이스를 작성한다. 구현 코드, 기준 계약, 공용 라이브러리, 다른 Agent 정의는 건드리지 않는다.
- 가지 모드로만 동작한다. 작성(Spec에 선언됐는데 아직 없는 테스트를 채움) 정렬(실제 계약과 어긋난 기존 테스트를 맞춤).
- 종료 조건은 "통과"아니라 "Spec 충족"이다. Spec이 선언한 흐름과 검증 항목을 선언된 강도 그대로 채웠을 때만 끝난 것으로 본다.
- 작성할 대상 = "Spec이 선언한 테스트" "이미 존재하는 테스트".  차집합만 채운다.
- Spec에서 필수 흐름으로 지정된 곳은 강화된 형태로 반드시 검증하고, 일반 흐름도 적당히가 아니라 구체화된 수준으로 작성한다.
- happy case도 최소한으로 멈추지 않는다. Spec에 선언된 응답 스키마의 모든 필드를 검증하고, 상태 전이가 선언돼 있으면 마지막 상태까지 따라가 확인한다.
- 통과시키려고 단언(assertion)약화시키지 않는다. 구현이 진짜 잘못된 같으면 테스트를 고치지 말고 우려 사항으로 올려 사람의 판단을 받는다.
- 번에 하나(작성은  묶음, 정렬은 테스트) 다룬다. 판단이 필요한 변경을 한꺼번에 펼치지 않는다.
- 기존 테스트의 스타일을 따른다. 새로 쓰기 전에 이웃 테스트를 먼저 읽는다.
- (기타 pytest 스크립트 작성 가이드 구체적인 테스트 스크립트 작성 기준 

명확하게 역할을 구분 짓고, 애매하게 정의되는 부분을 없애고, 최종 결과(구체화된 테스트 케이스 작성)까지 도달하도록 하였습니다. 물론 사람이 개입되어야 할 부분도 정확하게 명시했고요.

실제로 돌고 있는 코드에서 — 검증된 것만

바뀐 방식으로 테스트 케이스 작성을 요청하였습니다. 개발 문서를 통해 작성된 Spec은 개발자의 리뷰를 거치게 되어 더 이상 모호한 기준으로 테스트 케이스가 작성되지 않게 되었습니다. 그리고 Test-writer의 세부적인 테스트 케이스 작성 기준은 테스트 케이스를 더욱 정확하고 세부적으로 만들었습니다.

발견된 버그 — Spec과 구현의 어긋남

작성된 케이스에서는 one-shot 방식으로는 찾기 어려운 사례도 도출되었습니다. 이번에는 위의 인덱스 생성과는 다른 API — 파일을 비동기로 처리하는 작업(Task)의 상태 조회 엔드포인트 — 에서 나온 사례입니다.

# Task 상태 조회 API — Spec 문서 기준: 완료 상태는 "SUCCESS"
assert body.get("status") == "SUCCESS", body

# ============ FAILED ============
# AssertionError: {"status": "completed", ...}
# 문서는 SUCCESS, 구현은 completed — Spec과 구현이 어긋나 있었다

AI를 통해 구현된 코드에서는 Status의 완료를 소문자(completed)로 정의하고 있었습니다. AI로 개발될 때 Status 관리가 구체적으로 관리되지 않았거나, 혹은 Context 유실로 이렇게 개발이 된 것 같습니다. 하지만 실제 API Spec 문서에는 완료 상태가 대문자(SUCCESS)로 정의되어 있었습니다. 만약 Client 개발자가 API Spec 문서만 믿고 연동을 구현했다면, 영원히 '완료' 상태를 인지하지 못하고 timeout 실패로 처리되는 버그가 발생했을 것입니다.

그래서 적당히 status code 200만 점검했다면, 아마 버그 리포트가 올라오기 전까지 발견하지 못했을 수 있습니다. 테스트 코드에서는 정확한 상태를 확인하는 검증 단계가 꼭 필요합니다. 이건 Test-writer의 강화된 기준을 통해 달성할 수 있었습니다. 이 어긋남은 리뷰를 거쳐 Spec의 Drift 항목으로 기록되었고, 상태 어휘를 통일하는 것으로 정리되었습니다.

개선된 수치 — 양보다 질

다음 표의 Before는 Agent에게 처음부터 끝까지 다 맡겼을 때의 Test Suite이고, After는 새로운 기준으로 재구축한 결과입니다. 핵심은 개수가 늘어난 게 아니라, 테스트의 구조와 커버리지의 질이 개선되었다는 점입니다.

                    Before(평면)              After(spec-driven)
────────────────────────────────────────────────────────────────────
테스트 파일          18 (폴더)             12기능 그룹으로 분리
기준 문서(spec.md)   없음                      그룹마다 1
테스트 함수          248*                    151 (spec과 1:1, 중복 없음)
공통 코드           파일마다 복붙               lib/ 공용 모듈로 통합
전체 코드량          7,6345,669 (−26%)
────────────────────────────────────────────────────────────────────
* 이미 삭제된 기능·내부 서비스 대상 테스트 31개는 제외한 비교
tests/e2e/
├── conftest.py          # 공용 픽스처 (게이트웨이 클라이언트·인증)
├── lib/                 # 공용 헬퍼 (클라이언트·팩토리·폴링)
├── indexes/
│   ├── indexes.spec.md  # ← 기준(정답지)
│   └── test_*.py        # spec을 기준으로 작성된 테스트
├── tasks/
│   ├── tasks.spec.md
│   └── test_*

개수만 보면 테스트는 오히려 줄었습니다. 하지만 기존 스위트에서는 같은 "생성 → 적재 → 검색" 왕복을 스모크·여정·시나리오라는 이름만 바꿔 반복하는 테스트가 73개였고, 6개는 skip 처리된 채 방치돼 있었습니다.

기준 문서가 없으니 무엇을 커버하는지 셀 방법도 없었죠. 재구축 후에는 모든 테스트가 spec에 선언된 흐름과 1:1로 대응하고, 작성 대상이 "spec이 선언한 테스트 − 이미 존재하는 테스트"라는 차집합으로 정해지기 때문에 중복이 구조적으로 생기지 않습니다. 공통 코드도 정리됐습니다.

파일마다 복사되던 HTTP 클라이언트(39곳)와 폴링 루프(5곳)가 공용 모듈 하나로 모이면서 전체 코드는 26% 줄었고, 테스트별 리소스 격리 덕분에 병렬 실행도 가능해졌습니다.

마치며

저는 이 접근 방식을 Spec-Driven Test Automation이라고 정의했습니다.

Spec을 기반으로 코드를 생성하는 Spec-Driven Development와는 다르게, 여기서는 사람이 확정한 Spec을 정답으로 삼고, 테스트 자동화가 그 Spec을 지속적으로 검증하도록 만드는 방식에 가깝습니다.

즉, AI가 임의로 “적당한 테스트”를 작성하게 하는 것이 아니라, 사람이 고정한 Spec을 기준으로 테스트 범위와 검증 기준이 흔들리지 않도록 유지하는 접근입니다.

완전 자동화는 어렵습니다. 하지만 그건 AI가 모자라서가 아니라, 무엇이 옳은지를 정의하고 책임지는 일이 — 적어도 지금은 — 사람의 몫이기 때문입니다. Agent가 잘하는 것과 사람이 반드시 해야 하는 역할을 구분 짓고, 그 경계를 분명히 하는 순간, 자동화는 비로소 믿을 만해집니다.


팀과 여정을 함께할 분들을 찾고 있습니다 → [TwelveLabs Careers]