UnitTest 방치에서 MCP 시나리오 테스트까지 — AI로 갈아탄 테스트 자동화

UnitTest 방치에서 MCP 시나리오 테스트까지 - AI로 갈아탄 테스트 자동화

UnitTest 방치에서 MCP 시나리오 테스트까지 — AI로 갈아탄 테스트 자동화

안녕하세요. 물먹고하자입니다.
이번 글은 테스트 자동화를 여러 번 갈아탄 이야기예요. (AI테스트 활용기네요)
UnitTest → 명세 대조 → XCUITest → MCP 시나리오 테스트까지, 한 방에 정답을 찾은 게 아니라 계속 벽에 부딪히며 방향을 바꿔온 과정을 정리해봤어요.

1. AI 도입 전 — 손으로 짠 UnitTest, 결국 방치됐어요

처음엔 정석대로 UnitTest 코드를 직접 짜서 돌렸어요. 문제는 유지보수였습니다. 기능이 바뀌면 테스트도 같이 업데이트돼야 하는데, 바쁘다 보면 그 갱신이 스멀스멀 누락되더라고요. 깨진 테스트가 하나둘 쌓이면 신뢰가 떨어지고, 결국 "나중에 고치자" → 안 보게 되는" 전형적인 흐름을 탔어요.

⚠️ 자주 겪는 패턴
테스트 코드가 프로덕션 코드와 따로 논다는 게 핵심 문제였어요. 같이 관리 안 되면 테스트는 자산이 아니라 부채가 되더라고요.

2. 그래서 코드 대신 "명세 대조"로 (spec-test)

그래서 유닛 레벨 검증은 테스트 코드를 짜는 대신, 명세(Given / When / Then)를 마크다운으로 적어두고 AI에게 "현재 코드가 이 명세를 지키는지" 정적으로 대조시키는 방식으로 바꿨어요. 코드는 안 건드리고 리포트만 뽑으니 부담이 확 줄었죠.

✅ 준수코드가 명세대로 동작
⚠️ 불확실코드는 있으나 런타임 검증 필요
❌ 위반명세와 다르게 구현됨
🔲 미구현아직 빈 스텁 상태

유지보수 대상이 "테스트 코드"가 아니라 "명세 문서"로 바뀌니, 갱신 누락 스트레스가 훨씬 덜했어요.

3. UITest는 시작도 못했는데… XCUITest의 벽

사실 UI 테스트는 솔직히 시작도 못한 케이스였어요. 손이 많이 가서 계속 미뤄뒀죠. AI를 도입하고 나서야 "이참에 해보자" 하고 XCUITest를 구성하게 했는데, 여기서 또 벽을 만났어요.

아시다시피 XCUITest로 요소를 클릭하려면 접근성 식별자 세팅(예: accessibilityIdentifier / setupAccessibility())이 되어 있어야 잡히거든요. 그 말은 지금 만들어진 화면 + 앞으로 만들어질 화면 전부가 그 세팅을 고려해야 한다는 뜻이었어요.

⚠️ 부담 포인트
물론 AI한테 시켜서 접근성 세팅을 넣을 순 있어요. 근데 신규 작업마다 "그거 넣는 걸 까먹을" 가능성이 보였어요. 테스트 때문에 프로덕션 코드에 지속적으로 뭔가를 심어야 한다는 게 결국 1번(UnitTest)과 같은 함정처럼 느껴졌죠.

4. 방향 전환 — MCP로 앱을 "밖에서" 조작

그래서 다른 방법을 찾다가 MCP(에이전트가 외부 도구를 쓰게 해주는 연결 규격)를 활용하게 됐어요. 핵심은 앱 코드에 손대지 않고(접근성 세팅 없이) 밖에서 화면을 조작하는 거예요. 두 개를 붙였어요.

XcodeBuildMCP시뮬레이터 빌드/실행 + 네이티브 소스 영역 컨트롤 (접근성 트리 + tap·type·swipe)
Appium MCPWebView 내부 DOM 컨트롤 (NATIVE_APP ↔ WEBVIEW 컨텍스트 전환)

특히 하이브리드(WebView) 화면은 네이티브 접근성 트리로는 안이 텅 비어 보여서, Appium으로 웹뷰 컨텍스트에 붙어 textarea에 입력하고 응답 DOM을 읽는 식으로 풀었어요. 프로덕션 코드에 테스트용 세팅을 심을 필요가 없다는 게 XCUITest 대비 가장 큰 해방감이었어요.

중간에 방향을 한 번 더 틀었어요 — 네이티브도 Appium으로 통일
처음엔 네이티브 화면은 HID(키코드) 레벨 도구로, 웹뷰만 Appium으로 나눠서 했어요. 근데 네이티브 쪽이 오히려 웹뷰보다 훨씬 느렸어요. 범인은 한글 입력이었어요. HID는 한글을 못 넣어서 두벌식 자판 시퀀스로 조합하고(예: rlawldnr → 김지욱), IME 상태가 어긋나면 지우고 다시 치는 보정까지 해야 했거든요. 한 글자 넣는 데 몇 배씩 걸렸죠. 그래서 네이티브도 Appium 한 컨텍스트(NATIVE_APP)로 통일했더니, set_value한글이 그냥 바로 들어갔어요. 같은 시나리오가 수 분 → 약 1분으로 줄었어요.
🎬 XcodeBuildMCP — 네이티브 조작 데모
🎬 Appium MCP — WebView DOM 제어 데모

5. 시나리오 테스트, 그리고 시간을 줄이는 반복 학습

이렇게 해서 드디어 시나리오 단위 테스트가 가능해졌어요. "이 화면 들어가서 → 입력하고 → 결과 확인" 같은 흐름을 명세로 적어두면 AI가 그대로 실행하고 판정해요.

처음엔 시나리오 하나에 약 16분이나 걸렸어요. 근데 뜯어보니 앱이 실제로 일한 시간은 몇 초뿐이고, 대부분이 자동화 오버헤드(스크린샷을 매번 찍어 눈으로 판독 · 매번 재로그인 · 스텝마다 왕복)였어요. 그래서 반복 학습으로 이 부분을 계속 줄여나갔어요.

시나리오 1개 소요 시간 — 반복 최적화 전 → 후
도입 초기
약 16분
1차 개선
약 6분
반복 최적화 후
1~2분

※ 앱 실제 동작 시간(서버 응답 등)은 그대로고, 줄인 건 전부 자동화 오버헤드예요.

줄인 방법은 크게 세 가지였어요.

  • 한글 입력을 HID 두벌식 → Appium set_value 직접 : 네이티브에선 이게 제일 컸어요(두벌식 조합·IME 보정 통째로 제거)
  • 스크린샷 판독 → 텍스트 판정 : 화면을 눈으로 보는 대신 DOM·접근성 트리 텍스트로 판정
  • 재로그인 제거 : 자동로그인 켜두고 세션 재활용 → AI가 로그인 조작을 아예 안 함
  • 스텝 체이닝 : 로그인·진입 같은 결정적 스텝은 스크립트로 한 번에 묶어 AI 왕복 제거
핵심 교훈
"AI가 눈(스크린샷)으로 보며 한 스텝씩" 하는 게 제일 느리다. 판정을 텍스트로 바꾸고 결정적 스텝을 묶는 것만으로 시나리오당 1~2분까지 떨어졌어요.

6. 실측하며 만난 함정 — 특히 오판하기 쉬운 것

⚠️ 오판 주의
  • 롱프레스가 탭으로 오인식 — HID 터치로 길게 누르면 탭으로 먹혀 엉뚱한 화면이 열렸어요. Appium long_pressduration을 명시(1.3초)하니 선택 모드 진입이 안정됐어요.
  • 조작이 이상하면 코드 탓 전에 "세션/서버"부터 의심 — 삭제·읽음이 자꾸 안 먹혀서 좌표 조작을 의심했는데, 진짜 원인은 같은 계정이 다른 기기에서 로그인(중복 로그인)돼 세션이 만료된 거였어요. 서버가 요청을 거부하는 중이라 화면만 봐선 오판하기 딱 좋았죠. UI 자동화에서 제일 헷갈리는 지점이에요.
  • 좌표 조작은 상태 검증을 꼭 같이 — 각 조작 뒤 "예상한 화면이 됐는지"를 확인 안 하면 오탭 한 번에 통제를 잃어요. 게다가 좌표는 기기 해상도가 바뀌면 어긋나서, 가능하면 좌표보다 셀렉터를 우선해야 해요.

마무리

돌아보면 테스트 자동화는 "어떻게 짜느냐"보다 "어떻게 안 방치되게 하느냐"의 싸움이었어요. UnitTest는 프로덕션과 따로 놀아서 방치됐고, XCUITest는 프로덕션에 계속 세팅을 심어야 해서 부담이었죠. MCP 방식은 앱 코드는 그대로 두고 시나리오(명세)만 관리하면 돼서, 그 함정에서 비교적 자유로웠어요.

아직 완성형은 아니에요. 대신 "시나리오를 글로 적으면 실행된다"는 방향이 마음에 들어서, 실측하며 발견한 함정을 명세에 계속 쌓아가는 중이에요.

오늘은이만
즐거운코딩 되세요~

끝.

댓글