본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 08. 18:05

테스트 모드를 만들었습니다. 그리고 그것이 고장 났다는 사실을 발견했습니다.

요약

AI 생성 코드의 신뢰성을 검증하기 위해 구축한 '테스트 모드'의 개발 과정과 시행착오를 다룹니다. 단일 파일 업로드 방식의 한계를 깨닫고, 프로젝트 전체 구조를 보존하기 위해 ZIP 파일 기반의 워크스페이스 관리 방식으로 전환한 해결책을 제시합니다.

핵심 포인트

  • AI 생성 코드는 단순 파일이 아닌 전체 레포지토리 구조를 가짐
  • 단일 파일 업로드 방식은 복잡한 프로젝트의 의존성 문제를 야기함
  • ZIP 파일 압축 해제를 통해 프로젝트 디렉토리 구조를 보존함
  • 언어에 구애받지 않는 범용적인 테스트 실행 API 구현

jhansi.io를 공개적으로 빌드하는 과정 중 일부.

테스트 모드(Test mode)는 간단해 보였습니다. 코드를 업로드하고, 명령어를 전달하면, jhansi가 이를 실행하고 사용자의 테스트 스위트(test suite)를 돌려줍니다. 끝입니다.

하지만 끝이 아니었습니다. 첫 실행 결과: 빈 출력. 에러 없음. 그저 침묵뿐이었습니다.

무엇이 고장 났는지 — 그리고 이것이 우리가 AI 생성 코드(AI-generated code)를 생각하는 방식을 어떻게 바꾸었는지 소개합니다.

원래의 아이디어

AI는 코드를 작성합니다. 스크립트, API, 전체 백엔드까지 말이죠. 하지만 증명이 없는 코드는 부채(liability)입니다.

테스트 모드는 그 증명입니다. jhansi 샌드박스(sandbox)에 프로젝트를 업로드하고, 앱을 시작하는 명령어를 전달하면 jhansi는 다음과 같이 동작합니다:

  1. 명령어를 실행합니다.
  2. 서버가 올라올 때까지 기다립니다.
  3. 서버를 대상으로 테스트 스위트(test suite)를 실행합니다.
  4. 결과를 반환합니다.
  5. 모든 것을 종료합니다.

이 모든 과정은 격리된 컨테이너(container) 내부에서 이루어집니다. 아무것도 빠져나가지 않고, 아무것도 남지 않습니다.

이것은 Cursor, Claude Code, Windsurf에 결여된 검증 계층(verification layer)입니다. 그들은 생성하고, 우리는 검증합니다.

우리가 예상하지 못한 문제

테스트 모드 v0.4는 파일명을 허용했습니다.

app.py를 업로드하고, filename: "app.py"와 함께 exec를 호출하면, jhansi가 이를 실행하는 방법을 찾아냅니다.

문제는 실제 프로젝트는 단일 파일이 아니라는 점이었습니다.

A Flask 앱은 app.py + tests/ + requirements.txt로 구성됩니다. 우리가 이것들을 별도로 업로드했을 때, 그것들은 워크스페이스(workspace)에 평면적으로(flat) 배치되었습니다. pytest는 tests/를 찾을 수 없었습니다. 설치 프로그램은 requirements.txt를 찾을 수 없었습니다.

우리는 장난감 세계를 위해 테스트 모드를 만들었습니다. 하지만 AI는 장난감을 생성하지 않습니다. AI는 프로젝트를 생성합니다.

AI 에이전트(AI agents)는 hello_world.py를 작성하지 않습니다. 그들은 레포지토리(repos)를 작성합니다.

해결책: 프로젝트는 파일이 아니라 zip입니다

한 번 깨닫고 나면 명확합니다. 프로젝트 전체를 zip 파일로 업로드하는 것입니다.

# 프로젝트 내부에서
cd my_project && zip -r ../my_project.zip .

...

jhansi는 구조를 보존하며 이를 압축 해제합니다. tests/는 pytest가 예상하는 위치에 놓입니다. requirements.txt는 설치 프로그램이 찾는 위치에 놓입니다.

이로 인해 filename 파라미터도 폐기되었습니다. 이제 실제 명령어를 전달하면 됩니다:

curl -X POST http://localhost:8000/v1/sandboxes/sb_abc123/exec \
  -H "Content-Type: application/json" \
  -d '{"command": "python app.py", "test": true}'

언어에 구애받지 않습니다. Python, Node, Go, Java. 동일한 API를 사용합니다. jhansi가 런타임 (runtime)을 처리합니다.

테스트 모드가 실제로 하는 일

test: true일 때:

  1. 의존성 설치 (Install deps) — 블로킹 (blocking). pip install이 완료될 때까지 기다립니다. 이것이 버그 #2였습니다.
  2. 앱 시작 (Start your app) — 분리된 (detached) 상태로 백그라운드에서 실행됩니다.
  3. 2초 대기 (Wait 2s) — 서버가 포트 (port)에 바인딩 (bind)될 때까지 기다립니다.
  4. 테스트 실행 (Run tests)pytest, jest, go test, mvn test. 자동으로 감지됩니다.
  5. 출력 반환 (Return output)stdout, stderr, 테스트 요약.
  6. 컨테이너 종료 (Kill container) — 상태 유출 (state leaks)이 없습니다. 테스트 러너 (Test runner)는 설정이 전혀 필요하지 않습니다. pytest가 로컬에서 이를 찾아낸다면, 우리는 샌드박스 (sandbox) 내에서 이를 찾아냅니다.

의존성 레이스 컨디션 (The dependency race condition)

v1은 하나의 Docker 명령어로 설치와 앱 시작을 동시에 실행했습니다.

컨테이너 시작 → pip install 시작 → python app.py 시작 시도 → 2초 후 pytest 실행.

하지만 pip install flask가 여전히 다운로드 중이었습니다. 서버는 올라오지 않았습니다. 테스트는 ConnectionRefused 오류를 발생시켰습니다.

해결책: 이를 직렬화 (serialize)하는 것입니다.

  1. 의존성 설치. 완료될 때까지 블로킹 (Block).
  2. 앱 시작. 분리 (Detach).
  3. 2초간 수면 (Sleep).
  4. 테스트 실행. 지나고 보니 명백한 사실입니다. 이런 것은 제품을 출시하고 실패하는 것을 지켜봄으로써만 배울 수 있습니다.

솔직한 이야기

우리는 v0.4에서 테스트 모드를 출시했습니다. 작동합니다. 네 가지 언어 모두 엔드 투 엔드 (end-to-end) 테스트를 마쳤습니다.

하지만 여기까지 오기 위해서는 AI가 스크립트 (scripts)가 아닌 프로젝트 (projects)를 생성한다는 사실을 발견해야 했습니다.

첫 번째 설계는 데모를 위한 것이었습니다. 두 번째 설계는 AI가 실제로 만들어내는 세상을 위한 것입니다.

이것이 빌딩 인 퍼블릭 (building in public)이 중요한 이유입니다. 기능을 발표하기 위해서가 아니라, 문제를 건드렸을 때 그 문제가 어떻게 드러나는지를 기록하기 위해서입니다.

다음 단계

v0.5는 서브 모드 (serve mode)입니다 — 서버를 시작하고, 임시 프리뷰 URL을 받아 팀과 공유한 뒤, 작업이 끝나면 종료합니다.

실제 환경에 배포하기 전 마지막 검증 단계입니다. LLM으로부터

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0