쉼 없이 도는 테스트, 사람이 어디까지 돌봐야 할까요? - 토스닥터(Toss Doctor)
요약
토스 QA팀은 반복적인 스모크 테스트의 비효율성을 개선하기 위해 '토스닥터'를 개발했습니다. V2 버전에서는 Gherkin 문법으로 시나리오를 작성하고, LLM과 Appium MCP를 결합하여 코드를 자동 생성하는 방식을 도입했습니다. 또한, 화면 요소가 변경되어도 스스로 복구하는 스마트파인더 기능을 추가해 테스트 안정성을 높였습니다.
핵심 포인트
- Gherkin 문법을 사용해 사람이 '무엇'만 정의하고 도구가 코드를 생성합니다.
- LLM과 Appium MCP를 활용하여 실제 디바이스에서 스텝을 구현합니다.
- 요소 탐색은 스크린샷-UI 목록-XML 순의 3단계 접근 방식을 채택했습니다.
- 스마트파인더는 테스트 실패 시 자동으로 화면 요소를 찾아 복구하는 기능을 제공합니다.
토스 앱은 매주 새 버전을 내보냅니다. 그 과정에서 플랫폼별로 5~7개의 RC(Release Candidate, 배포 후보) 빌드가 쌓이고, 빌드가 나올 때마다 누군가는 회원가입부터 로그인, 자산, 송금까지 같은 화면을 손으로 눌러 봅니다. 핵심 기능이 망가지진 않았는지 빠르게 훑는 스모크 테스트죠. 빠지면 안 되는 일이지만, 매번 사람이 반복하기엔 품이 너무 많이 들어요.
안녕하세요, 토스 QA Platform 팀입니다. 저희는 이 스모크 테스트를 자동화한 토스닥터를 만들어 운영하고 있습니다.
토스의 기술 컨퍼런스 SLASH를 기억하시나요? '약은 약사에게, 테스트는 토스닥터에게'라는 세션으로 소개했던 그 도구가 바로 토스닥터 V1입니다. V1은 제 역할을 했습니다. 하지만 매주 돌리다 보니 불편이 쌓였어요. 테스트를 새로 짜는 데 손이 많이 갔고, 화면이 조금만 바뀌어도 테스트가 우수수 깨졌고, 깨진 뒤엔 "이게 왜 실패했지"를 사람이 하나하나 들여다봐야 했죠.
그래서 토스닥터 V2에서는 네 가지를 뜯어고쳤습니다. 코드를 만드는 법, 화면 속 요소를 찾는 법, 실패를 판단하는 법, 그리고 고치는 법. 그리고 이 네 가지가 실제 디바이스에서 끊김 없이 이어지도록 실행 환경도 다시 만들었습니다. 오늘은 그 이야기입니다. 그리고 이 V2가 지금 저희가 쓰고 있는 '토스체커'의 근간이 됐는데요, 그건 맨 뒤에서 짧게 이야기하겠습니다.
코드를 사람이 짜지 않게 됐습니다
예전엔 테스트 하나를 만들려면, 화면을 열어 요소를 찾고 클릭·입력·검증을 절차적으로 코드에 옮겨 적어야 했습니다. 이 테스트가 무엇을 검증하는지는 코드 속에 묻혀 있었고, 새 기능이 나올 때마다 같은 작업을 처음부터 반복했어요.
토스닥터 V2에서는 테스트를 두 단계로 나눴습니다. 먼저 사람은 '무엇을 검증할지'만 적습니다. 절차적인 스텝 대신, Gherkin 문법의 시나리오로요.
Given-When-Then은 사람이 읽어도 바로 이해되고, AI가 코드로 옮기기에도 좋은 형식입니다. 그다음 /codegen 명령이 이 .feature 파일을 실제 실행 코드로 변환합니다. LLM에 Appium MCP를 붙여, 실제 디바이스를 보며 요소를 찾아 스텝을 구현합니다. .feature에 남긴 주석은 그대로 구현 가이드가 돼요.
여기서 한 번 부딪힌 게 있는데요. 요소를 찾을 때 화면과 UI 트리를 통째로 AI에 넘겼더니 토큰이 감당이 안 됐습니다. 그래서 요소 탐색을 3단계로 나눠, 필요한 만큼만 찾게 바꿨습니다. 먼저 스크린샷과 화면에 드러난 요소 목록만으로 찾고(전체 UI 트리는 넘기지 않습니다), 안 되면 화면 구조 전체(XML)를 깊게 뒤지고, 그래도 안 되면 사람에게 물어요. 값싼 방법부터, 비싼 방법은 꼭 필요할 때만 찾습니다.
시나리오는 사람이 쓰고, 코드는 도구가 씁니다.

가입부터 송금·인증서·자산까지 앱의 핵심 흐름을 폭넓게 덮었습니다.
화면이 바뀌어도, 요소를 스스로 찾습니다
코드를 아무리 잘 짜도, 실행 중에 예상 못한 화면이 끼어듭니다. 어제 없던 이벤트 팝업이 뜨고, 버튼 이름이 확인에서 확인하기로 살짝 바뀌고, 심지어 앱 위로 외부 광고 웹페이지가 통째로 덮이기도 합니다. 이럴 때마다 테스트가 깨지면, 자동화는 오히려 사람 손이 계속 가는 짐이 돼요.
그래서 스마트파인더를 만들었습니다. 테스트가 요소를 못 찾고 실패하려는 바로 그 순간, 서버가 이 스킬을 호출합니다. 말하자면, 쓰러진 그 자리에서 곧바로 하는 심폐소생이에요. 스킬은 실패한 그 화면을 직접 들여다본 뒤 두 가지를 시도합니다.


실제로 이런 장면이 잡힙니다.
타행 계좌로 송금하는 스텝에서, 계좌번호 입력칸 위로 외부 앱 공유 툴팁이 떠서 화면을 가리고 있었습니다. 스마트파인더는 이걸 블로커로 판단해 툴팁을 닫고, 가림이 사라진 걸 확인한 뒤 원래 계좌번호 입력 스텝을 이어갔습니다.

그리고 한 번 살려낸 걸로 끝나지 않습니다. 성공한 기록은 JSONL로 쌓이고, 다음에 같은 화면·같은 스텝에서 막히면 과거 성공 사례를 먼저 참고합니다. 자주 나오는 팝업일수록 더 빨리, 더 정확히 넘겨요.
한 가지는 특히 조심했는데요. 검증(then) 스텝에서는 similar-match를 금지했습니다. '비슷한 요소를 눌러서 통과시키는' 건 여기선 거짓 성공을 만들고, 그게 다음 스텝으로 연쇄되기 때문이에요(실제로 5스텝이 줄줄이 거짓 성공한 적이 있었습니다). 그래서 검증 스텝에선 가리던 걸 치우는 것까지만 허용하고, 결과 자체는 눈속임하지 않습니다.
실패하면, 왜 실패했는지부터 판단합니다
테스트가 깨지는 건 자연스러운 일입니다. 중요한 건 그다음이에요.
토스닥터는 실패를 감지하면 그 순간의 정보를 모읍니다. 실패한 시나리오, 스텝 코드, 에러 메시지, 스크린샷까지요. 이걸 토션에 실시간으로 기록하고, 곧바로 분석(/diagnosis)을 돌립니다.
분석은 세 가지를 내놓는데요. 추정 원인, 필요한 조치, 그리고 recoverable 판정. 이 마지막 하나가 핵심이에요.

말로만 하면 와닿지 않으니 실제 사례를 살펴볼게요. 아래 두 에러는 같은 '송금 실패'인데, 한 번의 실행에서 서로 다른 recoverable 판정을 받았습니다.
하나는 코드로 고치면 되고, 하나는 코드 밖의 일이죠. 이 선을 긋는 게 분석의 일입니다.

스크린샷은 실패한 화면 하나만 보지 않습니다. _ERROR.png와 직전 스텝들의 화면을 시간 순서대로 함께 봅니다. 실패 화면만 보면 중간에 스쳐 간 툴팁·팝업·화면 전환 실패를 놓치기 때문이에요.
고칠 수 있는 건 스스로 고칩니다
recoverable이 true면, 토션의 분석 결과를 바탕으로 곧바로 복구 실행(/recovery)을 시작할 수 있어요. 평소의 전체 실행과는 목적이 다릅니다.

중요한 점은 멈추고 → 붙여서 → 확인한다는 것입니다. 전체 실행은 에러가 나도 전체 결과를 모으기 위해 계속 진행하지만, 복구 실행은 첫 에러에서 즉시 멈춰요. 멈춘 화면에 Appium MCP를 붙이면, 분석이 '추정'했던 걸 실제 요소를 보며 '확인'할 수 있습니다.
복구는 E-1부터 E-5까지 순서대로 흐릅니다. 앞의 타행 계좌 송금 예시를 이어가 볼게요.
- E-1 : pytest 재실행 → 같은 에러 재현 (
ImageViewlocator 타임아웃) - E-2 : 멈춘 화면을 Appium MCP로 확인 → '보낼까요'가
TextView임을 직접 확인 - E-3 : locator를
ImageView→TextView로 수정 - E-4 : 재실행 → 전체 시나리오 PASSED
- E-5 :
lessons.md에 회고 기록 → '보낼까요'는 ImageView가 아니라 TextView, 앱 업데이트 시 요소 타입 변경 주의
E-1부터 E-5까지, 사람이 손댄 곳은 없어요. 고칠 수 있다고 판단한 실패는 이렇게 스스로 고쳐 두고, 사람에게는 정말 사람이 봐야 할 실패만 남깁니다.
무대 뒤에는 서버가 있습니다

지금까지의 이야기에는 숨은 조력자가 하나 있는데요. 코드젠도, 스마트파인더도, 에러 분석과 복구도 스스로 깨어나지 않습니다. 누군가 제때 불러줘야 움직여요. 그 ‘누군가’가 토스닥터 서버입니다.
시작은 토션에서 빌드를 선택하고 실행하는 것부터예요. RC 빌드가 나오면 사람이 빌드를 고르고 실행을 요청합니다. 그다음부터는 서버의 일입니다. 빌드를 받아 디바이스에 설치하고, 테스트를 실행하고, 진행 상황을 실시간으로 전달해요.
여기서 중요한 건 테스트가 끝난 뒤 결과를 모아서 처리하는 방식이 아니라는 것입니다. 서버와 테스트 프로세스는 양방향으로 통신하면서, 스텝 하나가 끝날 때마다 결과를 주고받아요.
그래서 테스트가 요소를 찾지 못하면 그대로 실패 처리하지 않습니다. 멈춘 화면에서 서버에 도움을 요청하고, 서버는 그 순간 LLM과 Appium MCP를 연결해 화면을 확인해요. 스마트파인더가 복구할 수 있다고 판단하면 그 결과를 다시 테스트에 전달하고, 테스트는 원래 흐름을 이어갑니다.
실행이 끝나면 서버는 실패한 시나리오를 모아 분석을 돌리고, 결과를 처음 실행을 요청했던 곳에 다시 전달합니다. 시작한 곳에서 끝을 보는 거죠.
빌드 실행부터 테스트, 실패 대응, 결과 전달까지. 토스닥터 서버는 이 전체 흐름을 연결하는 실행의 중심입니다.
토스닥터에서, 토스체커로
토스닥터 V2에서 만든 네 가지 방식은 스모크 테스트 안에서만 머물지 않았어요. 한국어 BDD 시나리오, 실패에 스스로 대응하는 self-healing, 실기기 기반 실행. 이 아이디어들을 더 키우고 다듬어, 더 넓은 시나리오까지 검증하는 프레임워크로 발전시켰습니다. 저희는 이걸 토스체커라고 부릅니다.

스모크가 '숨이 붙어 있나'를 본다면, 리그레션은 '어디 아픈 데는 없나'를 봅니다. 더 넓고 깊은 검증을 사람 손으로 매번 하기는 어려워요. 토스닥터 V2에서 만든 네 가지 방식은 이 영역에서도 그대로 활용할 수 있었습니다.
실제로 토스체커는, 세 명의 테스터가 꼬박 이틀을 매달려 손으로 돌리던 배포 전 리그레션 테스트를 대신하기 시작했어요. 1,375개의 테스트 케이스를 기존 업무와 병행하며 3주 만에 자동화했죠. 토스닥터가 '한 앱의 스모크를 자동화한 도구'였다면, 토스체커는 '그 방식을 더 넓은 검증 영역으로 확장한 QA 자동화 플랫폼'입니다. 어떻게 해냈는지, 또 어디까지 넓혀갈지는 다음 아티클에서 자세히 다루겠습니다.
다시 처음으로 돌아가 볼까요. 매주 수십 개의 빌드를 사람이 손으로 확인하던 일에서 시작했습니다. 이제 코드를 만들고, 화면 속 요소를 찾고, 실패의 원인을 판단하고, 고칠 수 있는 문제를 고치는 일까지 도구가 맡습니다. 사람은 모든 테스트를 직접 돌보는 대신, 무엇을 검증할지와 어디까지 자동화할지를 설계하는 일에 집중할 수 있게 됐습니다. 토스닥터 V2가 다시 만든 건 단순히 테스트 자동화 도구 하나가 아니었어요. 다음 단계로 나아갈 수 있는 구조였습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 RSS: Toss Tech Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기