
일주일 동안 AI에게 내 앱을 55번 테스트하게 했다. 무엇이 망가졌고, 내가 무엇을 망가뜨렸는가
요약
AI 테스팅 에이전트인 TestSprite를 활용하여 일주일간 55번의 반복 테스트를 수행하며 얻은 실무 경험을 공유합니다. 개발자가 놓치기 쉬운 신규 사용자 시나리오와 에이전트의 탐색 한계를 극복하는 과정을 다룹니다.
핵심 포인트
- AI 에이전트는 실제 사용자처럼 새로운 계정을 생성하여 테스트함
- 기존 개발자가 놓치기 쉬운 리다이렉트 로직 및 빈 데이터 시나리오 발견
- 에이전트의 경로 탐색 실패 및 assertion 오류 대응 방법
- 배포 전 AI 에이전트를 루프에 포함하는 자동화 워크플로우의 효용성
나는 통과된 테스트를 가지고 있었다. 초록색 체크 표시, 내 스위트(suite)에 저장되었고, 볼 때마다 깨끗하게 실행되었다. "펫 스프라이트(Pet sprite)가 렌더링됨, 깨진 이미지 없음."
그런데 시크릿 창(incognito window)에서 앱을 열었더니 펫이 깨진 이미지 아이콘으로 나타났다. 모든 페이지에서 말이다. 며칠 동안 그랬던 모양이다.
테스트는 내내 초록색이었다. 이것은 어떻게 그런 일이 일어났는지, 그리고 내가 어떻게 대처했는지에 대한 이야기다. 하지만 동시에, 내가 내 코드를 신뢰하기 전에 AI 테스팅 에이전트(testing agent)가 내 라이브 앱을 사용하도록 허용한 나머지 54번의 라운드에 대한 이야기이기도 하다.
배경 설명 (some context)
나는 해커톤을 위해 Nora를 만들고 있었다. 간격 반복(spaced repetition), 파인만 학습법(Feynman explanations), 그리고 학습함에 따라 성장하는 가상 펫이 포함된 픽셀 아트 학습 앱이다. 약 50개의 라우트(routes), Next.js 16, Supabase를 사용했으며 Vercel에 배포되었다.
해커톤의 주제는 "루프를 구축하라(Build the Loop)"였다. 그래서 마지막에 한 번 테스트하는 대신, 나와 모든 배포(deploy) 사이에 TestSprite(AI 테스팅 에이전트)를 배치했다. 규칙은 다음과 같다: 기능을 작성하고, 에이전트가 클라우드 브라우저에서 라이브 앱을 열어 실제 사람처럼 사용하게 한 뒤, 판결을 받는 것이다. 통과(Pass) = 저장됨. 실패(Fail) = 스크린샷, DOM 스냅샷, 근본 원인 추측이 포함된 실패 번들(failure bundle)을 받음. 수정하고, 다시 배포하고, 다시 실행한다.
나는 일주일 동안 이 과정을 55번 반복했다. 여기서 내가 배운 점들을 소개한다.
루프가 실제로 작동하는 방식 (how the loop actually works)
네 가지 명령어로 이루어져 있다:
# 기능이 수행해야 할 내용을 설명하고, 라이브 앱에 대해 실행
testsprite test create --plan-from ./plans/signup.plan.json \
--type frontend --run --wait \
...
이것이 전부다. 복잡하지 않다. 흥미로운 부분은 그것이 무엇을 찾아내느냐 하는 것이다.
모든 신규 사용자가 보게 되었을 빈 페이지 (the blank page every new user would've seen)
나는 항상 내 계정으로 로그인한 상태에서 테스트한다. 내 계정에는 데이터가 있다. 그래서 내 대시보드는 아주 멋지게 보인다.
TestSprite는 실행할 때마다 새로운 계정을 생성한다. 새로운 계정 = 데이터 없음 = 나의 리다이렉트(redirect) 로직이 아무것도 없는 빈 /app 셸(shell)로 사용자를 던져버렸다. 온보딩(onboarding)도, 환영 메시지도 없이, 그저 하얀 화면뿐이었다.
모든 신규 가입자는 그 화면을 보고 떠났을 것이다. 해결책은 리다이렉트(redirect) 코드 한 줄이었다. 하지만 나는 결코 그것을 찾아내지 못했을 것이다. 왜냐하면 나는 이력이 전혀 없는 사용자의 입장에서 테스트를 해본 적이 없기 때문이다.
통과하는 데 4라운드가 걸린 분석(analytics) 페이지
1라운드: 404 오류. 리팩터링(refactor) 중에 경로(route) 이름을 변경하고 잊어버렸다.
2라운드: 경로를 수정했지만, 에이전트(agent)가 중첩된 사이드바(sidebar)를 통해 그곳으로 이동하지 못했다. 클릭하다가 길을 잃었다.
3라운드: URL로 직접 접속하라고 지시했다. 성공했다! 그러더니 활동량이 적은 계정에서는 렌더링(render)되지 않는 차트(chart)가 있는지 확인(assert)하기 시작했다.
4라운드: 단언(assertion) 부분을 수정했다. 여전히 실패했다.
문제는 테스트의 _이름(name)_이었다. 이름이 "Analytics page shows stats and charts(분석 페이지에 통계 및 차트 표시)"로 되어 있었다. 에이전트는 자신의 이름을 컨텍스트(context)로 읽었고, 더 이상 존재하지 않는 차트들을 계속해서 찾아 헤맸다.
나는 이름을 변경했다. 통과했다.
테스트의 이름은 프롬프트(prompt)의 일부이다. 테스트 계획을 업데이트하면서 이전 이름을 그대로 두면, 에이전트는 그 이름을 읽고 혼란에 빠진다. 테스트 도구를 통해 배운 기묘한 교훈이었지만, 머릿속에 깊이 박혔다.
거짓말하는 테스트
자, 이제 — 펫 스프라이트(pet sprite) 이야기다.
Nora에는 동반 펫(포켓몬 같은 스프라이트)이 있다. 나에게는 "펫 스프라이트가 깨진 이미지 플레이스홀더가 아닌 정상적으로 렌더링됨"이라는 검증된 테스트가 있었다. 이 테스트는 매번 초록색(성공)으로 실행되었다.
스프라이트들은 제3자 CDN에서 핫링크(hotlinked) 방식으로 가져오고 있었다. 그 CDN은 속도 제한(rate-limit)이 걸려 있었다. 어떤 때는 이미지를 제공하고, 어떤 때는 404 오류를 냈다. 내 테스트는 "상태가 좋은" 시간대에 실행되었던 것이다. 초록색 불은 실제였지만 무의미했다. 내 코드가 아니라 타인의 업타임(uptime)을 측정하고 있었던 것이다.
나는 39개의 스프라이트를 모두 로컬(local)에 직접 호스팅함으로써 이 문제를 해결했다. 그러고 나서 새로운 회귀 테스트(regression test)를 구축했다.
하지만 여기서 솔직하게 말하고 싶은 점이 있다: TestSprite는 이 버그를 잡아내지 못했다. 내가 직접 발견했다. 친구의 노트북으로 앱을 데모(demo)하던 중, 캐시(cache)가 없는 상태로 새로 열었더니 바로 나타났다. 곳곳에 깨진 이미지들이 있었다. 기존 테스트는 불안정한 통과(flaky passes)로 이 문제를 가리고(masking) 있었던 것이다. 나는 이를 도구가 잡아낸 것이 아니라, 반복 작업 로그(iteration log)에 수동으로 발견한 사항으로 기록했다.
만약 당신의 테스트 보고서가 도구가 찾아낸 것과 당신이 직접 찾아낸 것을 정직하게 구분하지 않는다면, 그 모든 것은 아무런 의미가 없습니다.
브라우저가 볼 수 없는 계층 테스트하기
이것은 나에게 진정으로 놀라운 경험이었습니다.
브라우저 테스트는 "페이지가 로드된다"는 것을 알려줍니다. 좋습니다. 하지만 데이터베이스가 실제로 권한이 없는 접근을 거부하는지 여부는 알려줄 수 없습니다. UI 상으로는 두 상황이 동일해 보이기 때문입니다. 페이지가 당신의 데이터를 로드하거나, 혹은 아무것도 로드하지 않거나 둘 중 하나일 뿐입니다. 외부에서는 RLS (Row Level Security)가 작동하고 있는지 아니면 고장 났는지 알 수 없습니다.
TestSprite에는 Playwright 대신 Python을 서버 측에서 실행하는 --type backend 모드가 있습니다:
testsprite test create --type backend \
--name "RLS rejects unauthorized reward manipulation" \
--code-file ./test_rls_security.py --run --wait
나는 이런 테스트를 7개 작성했습니다. 이 테스트들은 익명 사용자가 보상 RPC (Remote Procedure Call)를 호출할 수 없고, 다른 사용자의 프로필을 읽을 수 없으며, 가짜 플래시카드를 삽입할 수 없음을 증명합니다. 브라우저에서는 보이지 않는 것들입니다.
만약 당신이 Supabase를 사용하면서 외부에서 RLS를 테스트하는 무언가를 갖추고 있지 않다면... 당신은 그저 그것이 잘 작동할 것이라고 믿고 있는 것입니다. 뭐, 용감한 선택이라고 할 수 있겠네요.
내가 삭제한 테스트
나는 모바일 내비게이션 테스트를 작성했습니다. 컴포넌트는 내 휴대폰에서 잘 작동했습니다. 하지만 클라우드 러너 (cloud runner)는 고정된 데스크톱 뷰포트 (viewport)를 사용하며 크기를 조정할 수 없습니다.
그래서 테스트가 계속 실패했습니다. 내 코드가 틀려서가 아니라, 러너가 모바일 내비게이션을 볼 수 없었기 때문입니다.
나는 그 테스트를 삭제하고 로그에 그 이유를 정확히 기록했습니다. 테스트 스위트 (test suite)는 정직한 판결을 내릴 수 있는 테스트만을 포함해야 합니다. 인프라의 한계 때문에 실패하는 테스트는 당신이 빨간색(실패 신호)을 무시하도록 훈련시킵니다. 이는 테스트가 아예 없는 것보다 더 나쁩니다.
나 또한 CLI를 망가뜨렸고 (그리고 고쳤다)
나는 TestSprite CLI를 너무 많이 실행해서 그 안의 버그들을 발견했습니다. 출력을 삼켜버리는 타임아웃(timeout) 문제, 자격 증명(credential)의 엣지 케이스(edge case) 등이 있었습니다. 그래서 나는 이를 업스트림 (upstream)에 반영하여 수정했습니다. 10개의 PR (Pull Request)이 모두 머지(merge)되었습니다. 여기에는 테스트를 N번 재실행하여 안정성을 보고하는 test flaky라는 새로운 명령어도 포함되어 있습니다.
이는 "초록색 테스트가 나에게 거짓말을 하던 상황" 이후에 정확히 내가 필요했던 것이었습니다. 아이러니하군요.
MCP vs CLI vs portal — 솔직한 비교
저는 세 가지를 모두 시도해 보았습니다. 제가 실제로 정착한 결과는 다음과 같습니다:
MCP 서버 (IDE integration): 첫날부터 사용했습니다. AI 어시스턴트에게 "이 프로젝트를 테스트해 줘"라고 말하면 무엇을 테스트할지 스스로 파악합니다. 시작 단계에서 유용합니다. 하지만 특정 단언(assertion)을 포함한 반복 가능한 루프를 위해서는 CLI가 더 많은 제어권을 제공합니다.
CLI: 제가 주로 머물렀던 곳입니다. 워크플로우의 90%를 차지했습니다. 생성, 실행, 실패, 수정, 재실행. 스크립트 작성이 가능하며 CI/CD에서도 작동합니다. 이것이 핵심입니다.
웹 포털 (Web portal): 오직 포렌식(forensics) 용도로만 사용했습니다. 테스트가 실패했을 때 정확한 스크린샷, 그 순간의 DOM, 네트워크 로그를 확인할 수 있습니다. 디버깅(debugging)에는 유용하지만, 테스트를 생성하거나 관리하는 용도는 아닙니다.
최종 집계
- 55회 반복, 7일간의 빌드
- 57개의 테스트 확보 (프론트엔드 50개, 백엔드 7개), 모두 통과 (all green)
- 총 100회 이상의 실행
- 10개의 실제 제품 버그 발견 및 수정
- 1개의 테스트 삭제 (러너(runner)의 제한 사항, 솔직하게 기록함)
- CLI 자체에 10개의 PR(Pull Request) 머지
이 과정을 시작하려는 사람에게 실제로 해주고 싶은 말
낯선 사람처럼 테스트하게 하세요. 회원가입 버그가 존재했던 이유는 제가 오직 데이터가 풍부한 제 자신의 계정으로만 테스트했기 때문입니다.
차단된 실행(blocked runs)을 주의 깊게 읽으세요. blocked는 에이전트가 시나리오를 완료할 수 없었음을 의미합니다 (보통 단언(assertion)이 잘못된 경우입니다). failed는 앱이 고장 났음을 의미합니다. 서로 다른 문제입니다.
보이지 않는 계층을 테스트하세요. 보안이 데이터베이스 수준에서 이루어진다면, 브라우저 테스트로는 그것이 제대로 작동하는지 증명할 수 없습니다.
제3자 서비스에 의존하는 통과(green) 테스트를 불신하세요. 그것은 당신의 신뢰성이 아니라 타인의 신뢰성을 측정하는 것입니다.
정직해지세요. 저는 테스트 하나를 삭제했고, 수동으로 발견한 것을 수동이라고 명시했으며, "도구가 이것을 잡아내지 못했다"는 모든 순간을 기록했습니다. 그러한 정직함이 있었기에 나머지 54번의 반복 과정이 신뢰를 얻을 수 있었습니다.
앱은 오픈 소스입니다: github.com/lxcario/Nora. 전체 반복 로그: LOOP.md. TestSprite CLI: github.com/TestSprite/testsprite-cli.
만약 AI 기반 테스트 (AI-powered testing)를 시도해 보았거나, 불안정한 테스트 탐지 (flaky test detection)에 대한 생각이 있다면, 진심으로 의견을 듣고 싶습니다.
TestSprite Hackathon S3를 위해 제작되었습니다. 이 프로젝트는 1위를 차지했지만, 실제로 저에게 깊은 인상을 남긴 부분은 워크플로 (workflow)였습니다.]
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기