본문으로 건너뛰기
고급약 25분

Limited Service 재현 레시피 — 연결됨·인터넷 사용 불가 만들기

'연결은 되었으나 사용 불가' 상태를 원인별로 네 가지(극단 저속·DNS 실패·상위망 차단·완전 손실) 재현하고, 원인마다 달라야 할 기대 화면을 판정한다. 운영체제의 인터넷 연결 확인 원리까지 다룬다.

  • #LimitedService
  • #DNS
  • #캡티브포털
  • #오프라인
  • #재현레시피

개념

원인이 다르면 앱이 보여야 할 화면도 다르다

레슨 1에서 Limited Service를 별도 상태로 세운 이유를 다시 짚자. 단절은 신호가 명확하지만, Limited는 애매한 채로 오래 끌기 때문에 어렵다고 했다.

그런데 한 겹 더 들어가면, Limited Service 안에도 서로 다른 원인이 섞여 있다. 그리고 원인마다 앱이 보여줘야 할 화면이 다르다.

"인터넷이 안 돼요" 라는 같은 증상 뒤에
 
A  회선이 극단적으로 느리고 손실이 많다
   → 오래 기다리면 될 수도 있다 → "느린 연결" 안내 + 재시도
 
B  이름 해석(DNS)이 안 된다
   → 서버 주소를 못 찾는다 → "네트워크 오류" 안내
 
C  Wi-Fi는 붙었는데 그 위로 인터넷이 없다 (포털형·차단)
   → OS가 "인터넷 사용 불가" 를 이미 알고 있다 → 오프라인 배너 + 캐시 화면
 
D  패킷이 100% 사라진다 (연결 상태는 유지)
   → 즉시 실패가 아니라 타임아웃까지 기다린다 → 입력값 보존이 관건

네 가지를 모두 통과해야 "사용 불가 상태 대응이 되었다"고 판정한다. 하나만 테스트하고 끝내면 나머지 셋에서 사고가 난다.

공통 전제 — 이것부터 안 맞으면 아무것도 재현되지 않는다

□ 테스트 기기의 모바일 데이터 OFF
   → 켜져 있으면 Wi-Fi 품질 저하 시 셀룰러로 자동 전환된다
   → Limited 재현 실패의 1순위 원인 (레슨 2 원인 ⑤)
 
□ 앱 캐시 삭제 후 시작
   → 이전 응답이 남아 있으면 정상으로 보인다
 
□ 조건 적용 직후 속도 측정 결과를 스크린샷으로 남긴다
   → 레슨 2의 6단계 절차

레시피 A — 극단 저속 + 손실

만드는 법

방법설정
iOS NLC사용자 정의 — In Bandwidth 최소, Delay 1000ms, Dropped 30%
tc netemrate 100kbit delay 1000ms 200ms loss 30%
프록시최소 대역폭 + 지연

목표 수치는 다운 50200 kbps · 지연 1,000 ms 이상 · 손실 2040% 다.

기대 동작

  • 무한 스피너 없이 일정 시간 뒤 상태를 알리는 문구가 나온다
  • 자동 재시도 횟수와 간격이 통제된다 (무한 재시도 아님)
  • 수동 재시도 수단이 있다
  • 버튼을 연타해도 중복 요청이 생성되지 않는다
  • 일부만 받은 화면에서 레이아웃이 깨지지 않는다

"무한 스피너 없이"가 이 레시피의 핵심 판정 기준이다. 얼마나 기다려야 하는지 모르는 상태가 가장 나쁜 사용자 경험이고, 동시에 가장 흔하다.

관찰 포인트

이 조건에서는 요청이 성공도 실패도 아닌 상태로 오래 머문다. 그 시간 동안 앱이 무엇을 하는지 보는 것이 목적이다. 로그를 함께 보면 재시도가 몇 번 나가는지 셀 수 있다.

레시피 B — DNS만 지연·차단

왜 별도 레시피인가

DNS는 모든 요청의 앞단 에 있다. 여기가 막히면 서버가 멀쩡해도 아무것도 안 된다. 그리고 앱은 이 상황을 종종 엉뚱하게 처리한다 — "서버 점검 중입니다" 같은 잘못된 안내를 띄우거나, 아예 크래시한다.

만드는 법

방법설정
iOS NLCHigh Latency DNS 프로파일 (가장 간단)
NLC 사용자 정의DNS Delay 값을 크게
공유기DNS 서버를 응답 없는 주소로 지정
리눅스 APdnsmasq를 중지하거나 응답 없는 상위 DNS 지정

iOS NLC의 High Latency DNS 가 이 레시피를 사실상 한 번에 해결한다. 다른 방법으로는 DNS만 골라 막기가 의외로 까다롭다.

기대 동작

  • 도메인 해석 실패를 네트워크 오류로 안내 한다 (서버 오류로 오인하지 않는다)
  • 앱이 강제 종료되지 않는다
  • 재시도 시 DNS 캐시 때문에 영구히 실패하지 않는다
  • 다른 도메인(예: 이미지 CDN)만 실패할 때 화면이 부분적으로라도 동작한다

두 번째 항목이 실제로 자주 깨진다. DNS 해석 실패 예외를 처리하지 않아 크래시하는 앱이 생각보다 많다.

레시피 C — 인터넷 없음 (포털형·상위망 차단)

이 상태의 특징

Wi-Fi는 정상적으로 붙어 있고 로컬 통신도 된다. 그런데 그 위로 나가는 인터넷이 없다. 공용 Wi-Fi에서 로그인 페이지를 안 거친 상태, 사내 게스트망, 결제하지 않은 호텔 Wi-Fi가 전부 여기 해당한다.

OS가 이걸 어떻게 아는가

운영체제는 Wi-Fi에 붙을 때마다 정해진 주소로 작은 요청을 보내 인터넷이 살아 있는지 확인한다.

Android   http://connectivitycheck.gstatic.com/generate_204
          → 204 (본문 없음) 가 오면 정상
          → 다른 응답이 오거나 안 오면 "인터넷 사용 불가"
 
Apple     http://captive.apple.com/hotspot-detect.html
          → 예상한 내용이 오면 정상
 
Chrome    http://www.gstatic.com/generate_204

이 요청이 로그인 페이지로 가로채이면 OS는 캡티브 포털 로 판단하고 로그인 창을 띄운다. 응답이 아예 없으면 "인터넷 사용 불가" 로 표시한다.

QA가 알아야 할 실무적 함의가 있다. 이 확인 주소가 막힌 네트워크에서는 로그인 포털이 아예 안 뜬다. 사내 방화벽이 gstatic.com을 차단하면 게스트 Wi-Fi에 붙은 손님이 로그인 화면을 못 보고 "와이파이가 고장났다"고 말하게 된다.

만드는 법

방법설정
Windows 핫스팟핫스팟은 켜두고 PC의 인터넷 연결만 해제
공유기해당 기기의 외부 통신만 차단 (MAC 기반)
리눅스 APAP는 유지하고 업링크(eth0)를 내린다

핫스팟은 켜고 PC의 인터넷만 끊는 방법이 가장 간단하다. 폰은 여전히 Wi-Fi에 연결되어 있고 DHCP도 받았는데 밖으로는 못 나간다.

기대 동작

  • Android에서 '인터넷 사용 불가' 표시가 뜬다 (OS 레벨 확인)
  • 앱에 오프라인 배너가 노출된다
  • 캐시된 화면을 보여준다 (있다면)
  • 사용자 입력값이 보존된다
  • 연결이 돌아왔을 때 자동으로 복구되거나, 최소한 재시도 수단이 있다

추가 확인 — 캡티브 포털 대응

우리 앱이 공용 Wi-Fi에서 로그인 전 상태 를 만났을 때 어떻게 되는지도 확인 대상이다. 이때 서버 응답 대신 포털의 HTML 로그인 페이지가 돌아온다.

앱이 기대한 것:  {"data": [...]}
실제로 받은 것:  <html><head><title>Wi-Fi 로그인</title>...
 
→ JSON 파싱 실패 → 앱이 어떻게 반응하는가?

이 상황에서 크래시하는 앱이 드물지 않다. 레슨 4의 프록시로 "응답 본문을 HTML로 바꾸기"를 하면 쉽게 재현할 수 있다.

레시피 D — 완전 손실 (연결 상태는 유지)

단절과 어떻게 다른가

비행기 모드는 OS가 "네트워크 없음"을 즉시 알려준다. 앱은 오프라인 분기를 바로 탄다.

레시피 D는 연결 상태가 유지된 채로 패킷만 100% 사라진다. OS는 정상이라고 믿고, 앱은 요청을 보내고, 응답은 영원히 오지 않는다. 타임아웃까지 기다리는 경로 가 열린다.

만드는 법

방법설정
iOS NLC100% Loss 프로파일
tc netemloss 100%
ClumsyDrop 100%

기대 동작

  • 즉시 실패가 아니라 타임아웃 경로를 탄다 (그리고 그 타임아웃이 합리적이다)
  • 타임아웃까지의 시간 동안 사용자에게 진행 상태를 알린다
  • 입력값이 보존된다 — 작성 중이던 글, 입력한 폼이 사라지지 않는다
  • 타임아웃 후 재시도 수단을 제공한다
  • 재시도 폭주가 일어나지 않는다

입력값 보존이 이 레시피의 핵심이다. 긴 글을 쓰고 등록을 눌렀는데 타임아웃이 나면서 화면이 초기화되는 것은 사용자가 가장 화내는 결함 유형이다.

타임아웃이 합리적인가

여기서 자연스럽게 따라오는 질문이 "몇 초가 적절한가" 다. 정답은 기능마다 다르지만, 판단 기준은 있다.

사용자가 기다리는 화면(로그인, 결제 확인)   → 짧게. 10~15초
백그라운드 동기화                        → 길어도 된다. 30~60초
파일 업로드                             → 전체 타임아웃 대신 진행률 정체로 판단

타임아웃이 60초인데 사용자가 보는 화면이 스피너뿐이라면, 그건 타임아웃 값이 아니라 UI 설계의 문제 다. 레슨 7에서 타임아웃 예산을 더 자세히 다룬다.

네 레시피 한눈에 보기

A 극단 저속B DNSC 인터넷 없음D 완전 손실
연결 표시정상정상사용 불가정상
요청 결과아주 느리게 성공/실패이름 해석 실패연결 실패무응답 → 타임아웃
앱이 알 수 있는가어렵다예외로 구분 가능OS가 알려줌타임아웃으로만
핵심 판정무한 스피너 없음크래시 없음·올바른 안내오프라인 배너·캐시입력값 보존
가장 쉬운 재현tc netemiOS NLC High Latency DNS업링크 차단NLC 100% Loss

C만 OS가 상태를 알려준다는 점 이 중요하다. 나머지 셋에서는 앱이 스스로 판단해야 하고, 그래서 더 자주 틀린다.

실무에서 왜 필요한가

"오프라인 처리했습니다"를 검증할 수 있다

개발이 "오프라인 대응 완료"라고 하면 대개 비행기 모드만 확인한 상태다. 네 레시피를 돌려보면 셋 중 둘은 통과하지 못하는 경우 가 흔하다. 특히 B(DNS)와 D(완전 손실)가 자주 빠진다.

사고 유형별 원인을 구분할 수 있다

CS에 들어온 "인터넷이 안 돼요" 제보를 네 유형 중 어디인지 물어서 좁힐 수 있다.

"와이파이에 느낌표가 떠 있어요"        → C
"다른 앱은 되는데 우리 앱만 안 돼요"    → A 또는 D (또는 서버 문제)
"공용 와이파이에서만 그래요"           → C (캡티브 포털)
"지하철에서만 그래요"                 → A

크래시를 미리 잡는다

B(DNS 실패)와 C(HTML 응답)는 앱 크래시를 유발하는 대표적 조건 이다. 정상 응답만 상정하고 만든 파싱 코드가 여기서 터진다. 출시 전에 이 둘만 돌려봐도 값어치가 있다.

실습 과제

과제 1 — 네 레시피 전부 실행하고 판정표 채우기

우리 앱의 핵심 시나리오 하나(로그인 또는 주문)를 정하고, 네 조건에서 각각 실행해 판정표를 만든다.

레시피재현 성공?화면판정 기준 통과발견한 문제
A 극단 저속
B DNS
C 인터넷 없음
D 완전 손실

각 행에 조건 적용 확인 스크린샷 을 첨부한다.

과제 2 — 무한 스피너 찾기

레시피 A 조건에서 앱의 주요 화면 10개를 순회하며 일정 시간(예: 30초)이 지나도 아무 안내 없이 스피너만 도는 화면 을 찾는다.

찾은 화면마다 "몇 초 뒤에 무슨 문구가 나와야 하는가"를 제안으로 적는다. 이게 그대로 개선 요청이 된다.

과제 3 — DNS 실패에서 크래시 확인

레시피 B 조건에서 앱을 콜드 스타트하고, 주요 화면을 전부 순회한다. 크래시가 나면 스택 트레이스를 확보한다(모듈 17 레슨 4의 크래시 리포트 확인 방법을 쓴다).

크래시가 없다면 에러 메시지 문구가 정확한지 확인한다. "서버 점검 중"처럼 사실과 다른 안내가 나오면 그것도 결함이다.

과제 4 — 캡티브 포털 응답 재현

레슨 4의 프록시로 API 응답을 HTML 로그인 페이지로 바꿔 반환하고, 앱이 어떻게 반응하는지 확인한다.

Content-Type: text/html
본문: <html><body>Wi-Fi 로그인이 필요합니다</body></html>
  • 크래시하는가
  • 오류 메시지가 적절한가
  • "네트워크에 로그인이 필요합니다" 같은 안내가 가능한가

과제 5 — 입력값 보존 확인

레시피 D 조건에서 다음을 수행한다.

  1. 긴 텍스트를 입력하는 화면을 연다 (게시글, 리뷰, 문의 등)
  2. 충분히 긴 내용을 입력한다
  3. 등록·전송을 누른다
  4. 타임아웃이 날 때까지 기다린다
  5. 입력한 내용이 남아 있는가?

사라진다면 심각도 높은 결함으로 기록한다.

과제 6 — OS 인터넷 확인 동작 관찰

레시피 C를 걸어둔 상태에서 Android 기기의 Wi-Fi 상태 표시를 관찰한다. "인터넷 사용 불가" 표시가 뜨기까지 몇 초 걸리는지 재본다.

여유가 있다면 리눅스 AP에서 connectivitycheck.gstatic.com 으로 가는 요청이 실제로 나가는지 확인해본다(tcpdump 또는 dnsmasq 로그).

자가 체크리스트

  • Limited Service의 네 가지 원인을 구분해 설명할 수 있다
  • 원인마다 앱이 보여줘야 할 화면이 다른 이유를 설명할 수 있다
  • 네 레시피 각각의 가장 쉬운 재현 방법을 안다
  • 모바일 데이터를 꺼야 하는 이유를 다시 설명할 수 있다
  • 레시피 A의 핵심 판정 기준이 무엇인지 안다
  • DNS 실패가 왜 별도 레시피인지, 어떤 결함을 잡는지 설명할 수 있다
  • OS가 인터넷 연결을 확인하는 방식(생성 204 응답 등)을 설명할 수 있다
  • 캡티브 포털에서 앱이 HTML을 받는 상황과 그 위험을 설명할 수 있다
  • 레시피 D가 단절(비행기 모드)과 어떻게 다른지 설명할 수 있다
  • 입력값 보존이 왜 레시피 D의 핵심 판정 기준인지 안다
  • 네 레시피를 한 표로 비교하고 각각의 핵심 판정을 말할 수 있다

흔한 실수

비행기 모드 하나로 오프라인 테스트를 끝낸다. 가장 쉬운 조건이고, 앱이 가장 잘 대응하는 조건이다. 실제 사고는 나머지 셋에서 난다.

DNS 실패를 서버 오류로 안내한다. "서버 점검 중입니다"라는 문구가 나오면 사용자는 우리 서비스가 죽은 줄 안다. 실제로는 사용자 쪽 네트워크 문제다. 안내 문구가 원인을 잘못 지목하면 CS 문의가 늘어난다.

모바일 데이터를 안 끄고 재현이 안 된다고 한다. 레슨 2에서 이미 다뤘지만 Limited 레시피에서 특히 자주 반복된다.

HTML 응답을 상정하지 않는다. 공용 Wi-Fi 환경을 쓰는 사용자가 있는 이상 이건 반드시 일어나는 상황이다. JSON 파서에 방어가 없으면 크래시한다.

타임아웃만 확인하고 입력값은 안 본다. "오류 메시지가 잘 나온다"로 통과시켰는데, 사용자가 30분 쓴 글이 날아가고 있다.

재시도 횟수를 세지 않는다. 화면만 보면 "재시도 중"이라고만 나온다. 로그나 프록시 세션으로 실제 몇 번 나갔는지 세어야 폭주를 잡는다.

C를 만들 때 Wi-Fi 자체를 끈다. 그러면 단절이지 Limited가 아니다. Wi-Fi는 붙어 있고 업링크만 끊어야 한다.

참고 자료

  • Captive Portal Detection — Android 소스 문서 — 생성 204 응답 확인 방식과 판정 로직
  • Captive Portal Test URLs — 플랫폼별 인터넷 연결 확인 주소 목록
  • Understanding Captive Portal Auto-Detection Mechanisms — 네트워크 장비 관점에서 본 탐지 동작
  • 이 모듈 레슨 3 기기에서 직접 거는 저하 — NLC의 High Latency DNS 와 100% Loss 프로파일
  • 이 모듈 레슨 5 리눅스 AP + tc netem — loss 100% 와 업링크 차단으로 C·D를 만드는 방법
  • 이 모듈 레슨 7 저하 상태에서 확인할 것들 — 타임아웃 예산과 재시도 정책의 상세
  • 모듈 17 레슨 4 성능·크래시 모니터링 — B·C에서 발생한 크래시의 스택 트레이스 확보

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

DNS — 도메인 이름 체계
사람이 읽는 주소를 컴퓨터가 쓰는 번호로 바꿔 주는 전화번호부.
CDN — 콘텐츠 전송 망
이미지나 파일을 세계 여러 곳에 미리 복사해 두고 가까운 곳에서 내려 주는 구조.
OS — 운영체제
기기를 돌리는 바탕 프로그램 — 윈도우, 맥, 안드로이드 같은 것.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
HTML — 하이퍼텍스트 표시 언어
웹 화면의 뼈대를 적는 글자 형식. 제목·문단·버튼 같은 역할을 꺾쇠 괄호로 표시한다.
UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
CS — 고객 지원 · 칩 선택 신호
이 커리큘럼에서 두 가지로 쓰인다. ① 고객 문의와 불만을 직접 받는 조직 ② 여러 부품이 한 선을 나눠 쓸 때 "지금은 네 차례" 라고 하나를 고르는 신호. 앞뒤에 문의·고객이 있으면 ①, 통신·배선 이야기면 ②다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
JSON — 제이슨
프로그램끼리 값을 주고받을 때 쓰는 글자 형식. 괄호와 따옴표로 이름과 값을 짝지어 적는다.

용어집 전체 보기