앱을 만들고, 직접 눌러보고, 버그를 찾고, 고치고, 다시 눌러봅니다. 이 루프를 몇 번이나 반복하고 계신가요?
수동 QA는 반복 비용도 문제지만, 더 큰 문제가 있습니다. 사람은 정상 케이스만 테스트하는 경향이 있어요. 로그인이 되는지, 버튼이 눌리는지, 화면이 뜨는지. "잘 되는 것"을 확인하는 데 시간을 다 씁니다.
그런데 버그는 어디서 터질까요? 네트워크가 끊겼을 때, 빈 문자열을 넣었을 때, 버튼을 두 번 연타했을 때. "안 되는 경우"에서 터집니다. 사람이 일일이 이런 상황을 재현하고 검증하기엔 경우의 수가 너무 많아요.
이 글에서는 ADB로 앱 QA를 자동화하는 방법을 정리하면서, 핵심인 Spec-First 접근법을 함께 다뤄볼게요.
수동 QA
느린 반복
빌드 → 설치 → 직접 탭 → 확인
정상 케이스 위주
"잘 되는 것"만 확인하고 끝
사람이 놓치는 에러
네트워크, 권한, 동시성 케이스 누락
~30%
에러 커버리지
Spec-First + ADB
즉시 반복
스크립트 실행 한 번이면 전수 검증
에러 케이스 전수 검증
정의된 모든 시나리오를 빠짐없이
코드가 빠짐없이
귀찮거나 까먹는 일 없음
90%+
에러 커버리지
ADB(Android Debug Bridge)는 안드로이드 기기를 코드로 제어하는 도구입니다. USB나 Wi-Fi로 에뮬레이터에 연결하면, 사람 손 대신 코드가 앱을 조작할 수 있어요.
핵심 명령어만 정리하면 이렇습니다.
# 앱 실행
adb shell am start -n com.example.app/.MainActivity
# 앱 강제 종료
adb shell am force-stop com.example.app
# 앱 데이터 초기화 (로그아웃 상태로 되돌리기)
adb shell pm clear com.example.app
# 화면 탭 (x=540, y=960 좌표)
adb shell input tap 540 960
# 스와이프 (왼쪽으로)
adb shell input swipe 800 500 300 500
# 텍스트 입력
adb shell input text "hello@test.com"
# 뒤로가기 버튼
adb shell input keyevent 4
# 스크린샷 캡처
adb exec-out screencap -p > screenshot.png
# 현재 화면의 UI 구조 덤프
adb shell uiautomator dump /sdcard/ui.xml
adb pull /sdcard/ui.xml
# 앱 로그 확인
adb logcat -v time | grep "com.example.app"
# Wi-Fi 끄기 (네트워크 에러 테스트)
adb shell svc wifi disable
# Wi-Fi 켜기
adb shell svc wifi enable
# 권한 회수 (권한 거부 테스트)
adb shell pm revoke com.example.app android.permission.CAMERA
이 명령어들을 조합하면, 사람이 손으로 하던 모든 QA 동작을 스크립트로 재현할 수 있습니다. 탭, 입력, 스와이프, 스크린샷, 네트워크 차단까지 전부요.
그런데 여기서 한 가지 의문이 생깁니다.
ADB 명령어를 알면 자동화는 할 수 있어요. 하지만 "뭘 테스트할지"를 모르면 의미가 없습니다.
흔한 실수가 이겁니다. ADB 스크립트를 짜놓고, 정상 케이스만 돌리는 것. 로그인 성공, 화면 전환 성공, 데이터 로딩 성공. "잘 되는 것"만 확인하고 "QA 자동화 완료"라고 선언하는 거예요.
이건 수동 QA의 문제를 그대로 코드로 옮긴 것뿐입니다.
진짜 QA 자동화의 가치는 에러 케이스를 빠짐없이 검증하는 것에 있어요. 사람이 귀찮아서 안 하는 것, 깜빡하고 빠뜨리는 것, 경우의 수가 너무 많아서 포기하는 것. 이걸 코드가 대신해야 자동화의 의미가 있습니다.
핵심은 테스트 도구가 아니라 테스트 설계입니다. 뭘 테스트할지를 먼저 정의하지 않으면, 아무리 좋은 도구도 소용없어요.
QA 커버리지 비교
도구가 아니라 설계가 QA 품질을 결정한다
Spec-First는 간단합니다. 코드를 짜기 전에, 기능 명세를 먼저 쓰는 것. 그리고 그 명세에 정상 케이스뿐 아니라 모든 에러 케이스를 정의하는 거예요.
먼저 기능이 정상적으로 동작하는 시나리오를 정의합니다.
## 기능: 로그인
### 정상 케이스 (Happy Path)
1. 앱을 실행한다
2. 이메일 입력 필드에 유효한 이메일을 입력한다
3. 비밀번호 입력 필드에 올바른 비밀번호를 입력한다
4. "로그인" 버튼을 탭한다
5. 홈 화면이 표시된다 (3초 이내)
여기까지는 누구나 합니다. 차이를 만드는 건 다음 단계입니다.
에러 케이스를 체계적으로 분류하면, 빠뜨리는 걸 방지할 수 있어요. 모바일 앱에서 발생하는 에러는 크게 7가지 범주로 나뉩니다.
모바일 에러 7대 분류
입력값
빈 값, 형식 오류, 극단 길이
네트워크
끊김, 타임아웃, 느린 응답
권한
카메라, 위치 권한 거부
상태
백그라운드, 화면 회전
동시성
버튼 연타, 로딩 중 동작
인증
토큰 만료, 세션 충돌
데이터
빈 배열, null, 예상 외 형식
| 범주 | 설명 | 예시 |
|---|---|---|
| 입력값 | 잘못된 형식, 빈 값, 극단 값 | 빈 이메일, 특수문자만, 256자 초과 |
| 네트워크 | 연결 끊김, 타임아웃, 느린 응답 | Wi-Fi 꺼짐, 3G 환경, 요청 중 끊김 |
| 권한 | 시스템 권한 거부 | 카메라 권한 거부, 위치 권한 거부 |
| 상태 | 앱 생명주기 관련 | 백그라운드 → 포그라운드, 화면 회전 |
| 동시성 | 중복 동작, 타이밍 이슈 | 로그인 버튼 연타, 로딩 중 뒤로가기 |
| 인증 | 세션/토큰 관련 | 토큰 만료, 다른 기기에서 로그아웃 |
| 데이터 | 서버 응답 이상 | 빈 배열, null 필드, 예상 외 형식 |
이 분류 체계를 기능별로 적용하면, 이런 에러 정의서가 나옵니다.
| ID | 범주 | 시나리오 | 입력/조건 | 기대 동작 |
|---|---|---|---|---|
| E1 | 입력값 | 이메일 미입력 | "" | "이메일을 입력해주세요" 인라인 에러 |
| E2 | 입력값 | 이메일 형식 오류 | "abc" | "올바른 이메일 형식이 아닙니다" |
| E3 | 입력값 | 비밀번호 미입력 | "" | "비밀번호를 입력해주세요" |
| E4 | 입력값 | 비밀번호 틀림 | 올바른 이메일 + 틀린 비번 | "비밀번호가 일치하지 않습니다" + 재시도 |
| E5 | 네트워크 | Wi-Fi 꺼진 상태 | 정상 입력 | "인터넷 연결을 확인해주세요" + 재시도 버튼 |
| E6 | 네트워크 | 서버 타임아웃 | 정상 입력 | "서버 응답이 없습니다" + 재시도 버튼 |
| E7 | 동시성 | 로그인 버튼 연타 | 빠르게 2번 탭 | 요청 1회만 발생, 버튼 비활성화 |
| E8 | 인증 | 계정 잠금 | 5회 연속 실패 | "계정이 잠겼습니다. 비밀번호를 재설정하세요" |
| E9 | 상태 | 로딩 중 앱 백그라운드 | 로그인 요청 중 홈 버튼 | 복귀 시 결과 정상 반영 |
| E10 | 상태 | 로딩 중 화면 회전 | 로그인 요청 중 회전 | 크래시 없이 상태 유지 |
spec 한 줄이 자동 테스트 한 개입니다. 10개의 에러 케이스를 정의하면, 10개의 자동 테스트가 생깁니다. 정의하지 않은 에러 케이스는 테스트되지 않아요. 그래서 이 정의서가 QA의 핵심입니다.
에러 정의서가 있으면, ADB 테스트 스크립트는 기계적으로 만들 수 있습니다. 그리고 이 "기계적" 작업이야말로 AI가 잘하는 영역이에요.
#!/bin/bash
# happy_path_login.sh
echo "=== 로그인 정상 케이스 ==="
# 앱 초기화 & 실행
adb shell pm clear com.example.app
adb shell am start -n com.example.app/.LoginActivity
sleep 2
# 이메일 입력
adb shell input tap 540 400 # 이메일 필드 탭
adb shell input text "user@test.com"
# 비밀번호 입력
adb shell input tap 540 550 # 비밀번호 필드 탭
adb shell input text "Password123"
# 로그인 버튼 탭
adb shell input tap 540 700
sleep 3
# 검증: 홈 화면이 표시되는지 확인
adb shell uiautomator dump /sdcard/ui.xml
adb pull /sdcard/ui.xml
if grep -q "home_screen" ui.xml; then
echo "PASS: 홈 화면 진입 성공"
else
echo "FAIL: 홈 화면 진입 실패"
adb exec-out screencap -p > fail_happy_path.png
fi
spec 테이블의 E1(이메일 미입력)과 E5(네트워크 끊김)를 ADB로 변환하면 이렇습니다.
#!/bin/bash
# error_cases_login.sh
echo "=== E1: 이메일 미입력 ==="
adb shell pm clear com.example.app
adb shell am start -n com.example.app/.LoginActivity
sleep 2
# 이메일 입력 없이 비밀번호만 입력
adb shell input tap 540 550
adb shell input text "Password123"
adb shell input tap 540 700 # 로그인 버튼
sleep 1
# 검증: 인라인 에러 메시지 확인
adb shell uiautomator dump /sdcard/ui.xml
adb pull /sdcard/ui.xml
if grep -q "이메일을 입력해주세요" ui.xml; then
echo "E1 PASS"
else
echo "E1 FAIL"
adb exec-out screencap -p > fail_E1.png
fi
echo ""
echo "=== E5: Wi-Fi 꺼진 상태에서 로그인 ==="
adb shell pm clear com.example.app
adb shell am start -n com.example.app/.LoginActivity
sleep 2
# Wi-Fi 끄기
adb shell svc wifi disable
sleep 1
# 정상 입력 후 로그인 시도
adb shell input tap 540 400
adb shell input text "user@test.com"
adb shell input tap 540 550
adb shell input text "Password123"
adb shell input tap 540 700
sleep 2
# 검증: 네트워크 에러 메시지 확인
adb shell uiautomator dump /sdcard/ui.xml
adb pull /sdcard/ui.xml
if grep -q "인터넷 연결" ui.xml; then
echo "E5 PASS"
else
echo "E5 FAIL"
adb exec-out screencap -p > fail_E5.png
fi
# Wi-Fi 복구
adb shell svc wifi enable
패턴이 보이시나요? spec 테이블의 매 행이 하나의 테스트 블록으로 변환됩니다. 입력/조건 열은 ADB 명령어로, 기대 동작 열은 검증 로직으로.
이 변환 작업을 AI에게 맡기면 됩니다. spec 테이블을 주고 "이 에러 케이스를 ADB 스크립트로 만들어줘"라고 하면, 10개든 50개든 기계적으로 생성합니다.
Spec → ADB 변환
기능 Spec 테이블
에러 ID, 시나리오, 기대 동작
AI 변환
기계적 1:1 매핑
ADB 스크립트
입력 시뮬레이션 + 검증
spec 한 줄 = 테스트 한 개
여기서 중요한 건 AI에게 어떻게 시키느냐입니다.
이 기능 spec의 에러 케이스를 ADB 스크립트로 변환해줘.
- 각 에러 케이스마다 독립된 테스트 블록으로 작성
- 테스트 전에 앱 데이터 초기화 (adb shell pm clear)
- 검증은 uiautomator dump로 UI 텍스트 확인
- 실패 시 스크린샷 저장
- 네트워크 테스트 후에는 반드시 Wi-Fi 복구
spec이 정확하면, AI가 만드는 스크립트도 정확합니다. spec의 품질이 테스트의 품질을 결정하는 겁니다.
실제 기기 없이 에뮬레이터로 테스트하면 추가 이점이 있어요.
# 헤드리스 모드로 에뮬레이터 실행 (화면 없이)
emulator -avd Pixel_8 -no-window -no-audio &
# 에뮬레이터 준비될 때까지 대기
adb wait-for-device
# 스냅샷으로 2초 만에 특정 상태 복원
emulator -avd Pixel_8 -snapshot logged_in_state
스냅샷 기능이 특히 강력합니다. "로그인 완료 상태"를 스냅샷으로 저장해두면, 매 테스트마다 로그인 과정을 반복할 필요가 없어요. 2초 만에 원하는 상태로 복원하고 바로 테스트를 시작할 수 있습니다.
정리하면 이렇습니다.
도구가 아니라 설계가 QA 품질을 결정합니다.
ADB는 리모컨이에요. 리모컨을 아무리 잘 다뤄도, 뭘 눌러야 하는지 모르면 의미가 없습니다. 기능 spec이 그 "뭘"을 정의하는 문서입니다.
Spec-First QA 파이프라인
기능 Spec 작성
정상 케이스(Happy Path) 정의
에러 분류
7대 범주로 체계적 분류
에러 정의서
시나리오 + 입력 + 기대 동작 테이블
AI가 ADB 스크립트 생성
spec 테이블 → 자동 변환
에뮬레이터 실행
헤드리스 모드 + 스냅샷 활용
에러 90%+ 커버
나머지 10%에 집중할 여유가 생긴다
순서는 이렇습니다.
에러 케이스를 빠뜨리지 않고 정의하는 것. 이게 전부입니다. 입력값 경계, 네트워크 장애, 권한 거부, 상태 변화, 동시성 이슈까지 체계적으로 나열하면, 프로덕션에서 터지는 에러의 90%는 이 범위 안에 있습니다.
나머지 10%는 극히 드문 타이밍 이슈나 디바이스 특화 버그예요. 이건 어떤 방법론으로도 사전에 잡기 어렵습니다. 하지만 90%를 자동으로 잡을 수 있다면, 그 10%에 집중할 여유가 생깁니다.
에러를 먼저 정의하고, ADB로 검증하세요. 도구는 이미 준비되어 있습니다.
이런 변화를 함께 탐구하고 싶다면, 회원 가입해 주세요. 앞으로도 직접 써보고 발견한 것들을 이곳에서 나누려 해요. 😊