소프트웨어 테스트를 할 때 작성된 테스트 케이스에 맞춰 기능을 테스트하는 경우도 있지만,
정해진 패턴없이 무작위로 랜덤 데이터 입력으로 진행되는 테스트도 필요하다.

이러한 테스트를 일컫는 용어인 Fuzz Test

 

구글링을 해보면 주로 보안과 관련된 글이 많은 걸로 보아,
주로 보안 취약점을 발견하는 데 사용하는 테스트 기법인 것 같다.

 


 

Fuzz Test : AWS Devicefarm Built-in Fuzz Test

AWS Devicefarm에서 기본적으로 제공하는 Fuzz Test
랜덤 데이터 입력으로 소프트웨어/하드웨어의 취약점을 확인하는 테스트를 의미한다.

실제로 AWS Devicefarm에서 Built in Fuzz-Test 스크린 레코드를 보면 정신없이 화면이 움직이는 모습을 볼 수 있다.

 

 

AWS Devicefarm 이용 방법은 공식 홈페이지에 친절하게 나와있어서 생략한다.

 

Device Farm용 기본 제공 퍼지 테스트를 이용한 작업 - AWS Device Farm

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

이 테스트를 오프라인 단말에서 할 수 있는 방법이 없을까 찾던 중 알게 된

 

Monkey Test

Android adb에서 제공하는 Monkey Test tool

역시 친절한 안드로이드 문서에 설명이 너무 잘 되어 있다.

 

UI/Application Exerciser Monkey  |  Android 개발자  |  Android Developers

Monkey는 에뮬레이터나 기기에서 실행되는 프로그램으로 여러 시스템 레벨 이벤트뿐만 아니라 클릭, 터치, 동작과 같은 사용자 이벤트의 의사 랜덤 스트림을 생성합니다.

developer.android.com

아무것도 모르는 원숭이가 이것저것 막 눌러보듯이 테스트를 한다고 해서 Monkey Test다.

에뮬레이터와 실제 기기 모두 사용 가능하며, adb 기본 사용 설정이 되어 있어야 한다. ※ 아래 글 참고 ※

 

Android adb 환경변수 설정 (Feat. 연결된 기기 확인)

테스트 자동화 프로그램을 구축하면서 많이 쓰는 adb 명령어 ! adb 명령어를 사용하려면 환경변수 설정을 해줘야 한다. 사전준비 1. Android Studio SDK 설치 Download Android Studio and SDK tools  | Android..

summer-west.tistory.com

Monkey 를 사용하는 기본 구문은 다음과 같고, option을 설정해 나름 세세하게 랜덤 동작을 조절할 수 있다.

 

adb shell monkey [options] <event-count>

옵션에 대한 설명도 공식 홈페이지에 아주 잘 나와있지만, 주요 옵션에 대해서 참고사항을 적자면

 

동영상은 옵션이 -v 500 -s 500 이라 굉장히 빠르고 짧은 테스트지만
옵션값을 적절히 조절하면 Devicefarm 의 Fuzz Test와 유사한 테스트를 할 수 있다.

(비행기모드 / Wifi 설정 등도 드래그, 터치 랜덤 동작 중 나타나는 동작)

 

카카오엔터프라이즈 인턴을 하면서 QA 분야로, 정확하게는 자동화 엔지니어로 방향성을 잡았다.

다양한 방식으로 테스트 자동화 프로그래밍을 공부하고 있는데,

특히 AWS Devicefarm 과 APPIUM을 연동하면서 다양한 방식으로 APPIUM을 다뤄보고 있다.

그동안 아장아장 예제를 따라하며 시작했던 테스트 스크립트를 이제는 직접 필요한 부분만 작성 할 수 있게 됐다.

그 중에서 APPIUM 스크립트에서 가장 기본 설정이 되는 Desired Capabilities

스크립트 작성 방식, JSON 파일 방식, CLI 방식 중 상황에 맞춰 활용하면 꽤나 똑똑하게 테스트를 진행할 수 있다.

그래서 정리해보는 Desired Capabilities 설정 방법


 

Desired Capabilities

쉽게 말하면 테스트 환경 설정을 위한 속성값을 정의한다.

APPIUM 서버에서 Client를 연동할 때, 정의된 Desired Capabilities 값을 참조하여 테스트를 진행한다.

Desired Capabilities 작성 방식은 스크립트 작성 / JSON 이용 / CLI 방식 총 세가지 방식이 있다.

 

첫번째. 스크립트 작성 방식

제일 먼저 소개할 방식은 테스트 스크립트 작성 시 Desired Capabilities 속성을 정의하는 방식으로,

스크립트 언어에 따른 예시도 APPIUM 공식 홈페이지에 나와있어 가장 쉽게 접근할 수 있는 방식이다. (내 기준에서)

스크립트 최상단에 Desired Capabilities 속성을 먼저 정의하고, 해당 값으로 Driver를 시작한다.

아래 소개할 두 방식과 달리 유일하게 스크립트 내부에서 정의된 속성값을 호출할 수 있다는 장점이 있다.

출처 : Appium Officail Site (http://appium.io/docs/en/commands/session/create/) ​

위 속성값은 예시로 작성 된 속성일 뿐, 속성 목록을 보고 테스트 환경에 따라 필요한 속성을 추가/제거하여 작성하면 된다.

속성 목록은 APPIUM 공식 홈페이지에 어떤 역할인지, 예시 구문까지 아주 친절하게 나와있다.

 

Desired Capabilities - Appium

From here you can search these documents. Enter your search terms below.

appium.io

경험상 Android 테스트의 경우 기본적으로 platformName / platformVersion / appPackage / appActivity 가 필요하다.

(여러 단말이 연결된 경우 UDID까지)

※ apk 파일에서 패키지와 액티비티 정보를 추출하는 방법은 아래 글 참고

 

Android apk 파일에서 매니패스트 정보 추출 (패키지명, 권한 등)

apk 파일만 덩그러니 놓여있을 때, apk 파일의 매니패스트 정보를 추출할 수 있는 방법 ​ AndroidStudio 에서 제공하는 AAPT2 를 이용하는 방법으로 주로 패키지명, Activity 구조, 권한 정보 등을 추출할

summer-west.tistory.com

 

두번째. JSON 이용 방식

다음은 JSON 파일을 이용하는 방식으로, 스크립트 언어가 익숙치 않은 사람들에게 유용한 방식이라고 생각 된다.

APPIUM 서버를 실행할 때 --default-capabilities flag 옵션으로 Desired Capabilities를 정의할 수 있는데,

Desired Capabilities 속성이 정의된 JSON 파일명 입력만으로 손쉽게 정의할 수 있다.

다만 단점은 이 때 설정한 값은 처음 세션이 시작할 때만 적용되고, 이후 스크립트 내부에서는 해당 속성값을 사용할 수 없다는 것이다.

이 때 사용할 JSON 형식은 Object xpath, id 값을 찾는 Inspector Sesstion 자연스럽게 생성할 수 있다.

 

이렇게 생성된 JSON은 명령어에 직접 이용 혹은 JSON 파일로 저장하여 이용할 수 있다.

# 명령어에 직접 이용
APPIUM --default-capabilities '{"app": "myapp.app", "deviceName": "iPhone Simulator"}'

# Windows CMD 사용 시 \ 필수
APPIUM --default-capabilities "{\"app\": \"myapp.app\"}"

# JSON 파일 저장 후 이용
APPIUM --default-capabilities /path/to/file.json

 

세번째. CLI 방식

마지막은 CLI 속성을 이용하는 방식인데,
--app, --platform-version 등 속성값을 입력하면 자동으로 JSON 형식으로 변환해 Desired Capabilities를 정의하는 방식이다.
즉, JSON 이용 방식과 마찬가지로 스크립트 내부에서는 해당 속성값을 사용할 수 없다는 단점이 있다.

CLI 속성에는 Desired Capabilities 속성값 외 포트번호라든가, log 출력 형식 등 APPIUM 서버 동작을 위한 유용한 속성들도 많아서
참고해서 활용하면 훨씬 편리하게 테스트 자동화를 진행 할 수 있다.

 

CLI Arguments - Appium

From here you can search these documents. Enter your search terms below.

appium.io

 

※ Android App Bundle 형식은 아래 글 참고

 

Android App Bundle(.aab) 빌드 및 앱 설치 방법

AndroidStudio 에서는 빌드 결과물로 APK(Android 애플리케이션 패키지)와 AAB(Android App Bundle) 두 가지 형태를 제공한다. 흔히 앱 설치 파일로 알고 있는 APK는 Gradle에서 설정한 API 버전에 해당..

summer-west.tistory.com


Android Studio 상단 메뉴 > Build - Build Bundle(s) / APK (s) - Build APK(s) 클릭

 

​빌드가 끝나면 하단에 알림창 혹은 Event Log창에서 알림이 나타나고,
locate를 클릭하면 생성된 APK 디렉토리로 이동한다.

 

 

다들 알고 있듯이 이렇게 생성된 apk 파일은 안드로이드 단말에서 실행 시 앱이 설치 된다.

AndroidStudio 에서는 빌드 결과물로
APK(Android 애플리케이션 패키지)와 AAB(Android App Bundle) 두 가지 형태를 제공한다.

흔히 앱 설치 파일로 알고 있는 APK는 Gradle에서 설정한 API 버전에 해당하는 모든 설치 파일이 압축되어 있는 형태로,
Android 버전과 상관없이 동일한 파일로 앱을 설치한다.
반면, Android App Bundle은 다운로드 요청 시 사용자 단말 버전에 해당하는 API만 추출하여 빌드한 APK로 앱을 설치한다.
즉, Android App Bundle 형태로 빌드한 APK의 경우 필요한 API 버전만 포함하고 있어,
기존 방식의 APK 보다 현저히 작은 용량으로 앱을 제공한다.

실제로 Google Playstore에 앱을 등록할 때는 aab 형식을 권장하고,
테스트 과정에서 apk로만 테스트 할 경우 aab 설치 방식에서만 나타나는 이슈를 확인하지 못하는 경우도 있다.

그래서 알아본 aab 파일을 이용한 앱 빌드 및 설치 방법

 

 


Android App Bundle 빌드 방법

1. Android App Bundle 파일 생성

Android Studio 상단 메뉴 > Build  - Build Bundle(s) / APK(s) - Build Bundle(s) 클릭

 

2. aab 파일 디렉토리로 이동

일반적으로 C:\Users\사용자이름\AndroidStudioProjects\프로젝트이름\app\build\outputs\bundle\debug

Tip ! 빌드가 끝나면 하단에 알림창 or Event Log창에 알림이 뜨는데, locate 를 클릭하면 해당 디렉토리가 열린다.

 

 

Android App Bundle 앱 설치 방법

1. bundletool git hub 페이지에서 가장 최신 버전의 bundletool .jar 파일 다운로드

 

Releases · google/bundletool

Bundletool is a command-line tool to manipulate Android App Bundles - google/bundletool

github.com

aab 파일 위치로 bundletool.jar 다운로드


2. 명령 프롬프트(cmd) 실행 후 bundletool 버전 확인

cd aab 파일 디렉토리
java -jar bundletool파일명 version

예시)

/* 디렉토리로 이동 */
cd C:\Users\사용자이름\AndroidStudioProjects\프로젝트이름\app\build\outputs\bundle\debug
/* bundletool 버전 확인 */
java -jar bundletool파일명 version

예시)
Microsoft Windows [Version 10.0.18362.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\Username> cd C:\Users\Username\AndroidStudioProjects\Myapplication\app\build\outputs\bundle\debug
C:\Users\Username\AndroidStudioProjects\Myapplication\app\build\outputs\bundle\debug> java -jar bundletool-all-1.3.0.jar version
1.3.0

 

3. bundletool을 이용하여 APK 파일 설치

 

① 앱 설치할 스마트폰 USB 디버깅 모드로 PC 연결 ※ adb devices 명령어로 연결상태 확인

 

② bundletool로 연결된 스마트폰 버전에 맞는 APKs 생성 ※--connected-device 옵션을 생략하면, 모든 API 버전을 포함하는 APKs 생성

java -jar "bundletool파일명.jar" build-apks --connected-device --bundle="aab파일명.abb" --output="생성할apks파일명.apks"

③ 생성한 APKs로 스마트폰에 앱 설치

java -jar "bundletool파일명.jar" install-apks --apks="생성된apks파일명.apks"

 

예시)

/* 디렉토리로 이동 */
cd C:\Users\사용자이름\AndroidStudioProjects\프로젝트이름\app\build\outputs\bundle\debug
/* APKs 생성 */
java -jar "bundletool파일명.jar" build-apks --connected-device --bundle="aab파일명.abb" --output="생성할apks파일명.apks"
/* APK 설치 */
java -jar "bundletool파일명.jar" install-apks --apks="생성된apks파일명.apks"

예시)

Microsoft Windows [Version 10.0.18362.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\Username> cd C:\Users\Username\AndroidStudioProjects\Myapplication\app\build\outputs\bundle\debug
C:\Users\Username\AndroidStudioProjects\Myapplication\app\build\outputs\bundle\debug> 
java -jar "bundletool-all-1.3.0.jar" build-apks --connected-device --bundle="app-debug.abb" --output="generated.apks"
C:\Users\Username\AndroidStudioProjects\Myapplication\app\build\outputs\bundle\debug> 
java -jar "bundletool-all-1.3.0.jar" build-apks install-apks --apks="generated.apks"

※ 주의 사항 ※

App Bundle Build 시 debug 폴더 자체가 재생성되므로,
bundletool.jar 파일을 따로 저장해두고 빌드 시 debug 폴더로 다시 옮겨줘야 한다.

APKs 파일 생성 시 동일한 이름의 APKs 파일이 존재할 경우 에러가 발생한다.

 

참고 사이트 : Android 개발할 맛 나게 해주는 친절한 Android 개발자 문서

 

bundletool  |  Android 개발자  |  Android Developers

Android App Bundle을 빌드한 후에는 Google Play에서 어떻게 Android App Bundle을 사용하여 APK를 생성하는지, APK가 기기에 배포되었을 때 어떻게 작동하는지 테스트해야 합니다. App Bundle을 두 가지 방식으로

developer.android.com

 

 

AndroidStudio Gradle 설정 후에는 Manifest 연동을 위해 반드시 동기화가 필요하다.

방법은 아주아주 간단하다.

 


gradle 설정 파일을 수정하면 파일 위쪽에 노랑색 알림줄이 표시 되는데,

우측 끝에 Sync Now 버튼을 클릭한다.

 

그럼 똑똑하게 알아서 동기화가 진행되고, 아래와 같이 뜨면 완료.

 

AndroidStudio 는 프로젝트 빌드 도구로 Gradle 을 사용한다.

Gradle 은 Groovy를 기반으로 한 오픈 소스 형태의 빌드 자동화 도구로 DSL을 스크립트 언어로 사용한다.
실행할 처리 명령들을 모아 만든 태스크 단위로 실행하며,
이전에 사용했던 태스크를 재사용하거나 다른 시스템의 태스크를 공유하는 빌드 캐시 기능을 지원하여 향상된 빌드 속도를 제공한다.

​AndroidStudio 에서 프로젝트를 생성하면 기본적으로 gradle 빌드 구성 파일을 포함한다.

 

Android 앱 모듈의 기본 프로젝트 구조 (출처 : Android 개발자 사이트 )

build.gradle(Module: app) 예시  코드
apply plugin: 'com.android.application' // 프로젝트 패키지명

android {

  compileSdkVersion 28 // API 28 이하 버전 지원

  buildToolsVersion "29.0.2" // SDK 29.0.2 버전으로 빌드

  defaultConfig {

    applicationId 'com.example.myapp' // 앱 고유 식별 ID
    minSdkVersion 15 // 최소 지원 API 버전
    targetSdkVersion 28 // 테스트 시 이용할 API 버전
    versionCode 1 // 앱 버전
    versionName "1.0" // 앱 버전 이름
  }
  buildTypes {
    debug { // 디버그용 빌드
        applicationIdSuffix ".debug" // 디버그용 빌드 시 앱 ID 변형
    }
    release {
        minifyEnabled true // 코드 축소 사용
        shrinkResources true // 리소스 축소 사용
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }

  productFlavors {
    free { // free 버전 빌드 변형
      applicationId 'com.example.myapp.free' // 빌드 변형 시 앱 고유 ID
      applicationIdSuffix ".free" // 위와 동일
    }

    paid { // paid 버전 빌드 변형
      applicationId 'com.example.myapp.paid' // 빌드 변형 시 앱 고유 ID
      applicationIdSuffix ".paid" // 위와 동일
    }
  }
}

dependencies {
    implementation project(":lib") // 로컬 라이브러리 모듈 종속 항목 추가

    implementation 'com.example.android:app-magic:12.3' // 원격 바이너리 종속 항목 추가
    implementation group: 'com.example.android', name: 'app-magic', version: '12.3' // 위와 동일

    implementation fileTree(dir: 'libs', include: ['*.jar']) // 로컬 바이너리 종속 항목 추가
    implementation files('libs/foo.jar', 'libs/bar.jar') // 위와 동일
}

 

AndroidStudio 에서 Project 하나를 빌드하면 apk 파일 하나가 생성 된다.

즉, Project = 앱을 의미한다.

하나의 Project는 기본적으로 다음과 같은 구조로 생성된다.

 

Android 앱 모듈의 기본 프로젝트 구조 (출처 :  Android 개발자 사이트 )

 

사용자 인터페이스를 결정하는 리소스 파일(res 폴더)들과 앱의 동작을 구현하는 자바 파일(java)을 비롯해,

앱의 전반적인 정보(앱의 이름, 액티비티 구성, 권한 등)를 정의하는 Manifest 파일,

그리고 빌드를 위한 Gradle 스크립트 파일(build.gradle, setting.gradle)로 구성 된다.

이렇게 구성된 Project를 빌드하면 스마트폰에서 앱을 설치하는 APK 파일, 즉 Android 애플리케이션 패키지가 생성 된다.

 

일반적인 Android 앱 모듈의 빌드 프로세스 (출처 :  Android 개발자 사이트 )

 

컴파일러는 소스 코드를 스마트폰에서 실행할 수 있는 바이트 코드를 포함한 DEX 파일로 변환하고,

그 외 파일은 컴파일된 리소스 파일로 변환한다.

APK 패키저는 DEX 파일과 컴파일된 리소스 파일을 하나의 APK 파일로 결합한다.

앱 배포를 위한 APK 생성 시에는 Keystore를 이용하여 전자 서명을 진행해야 한다.

apk 파일만 덩그러니 놓여있을 때, apk 파일의 매니패스트 정보를 추출할 수 있는 방법

AndroidStudio 에서 제공하는 AAPT2 를 이용하는 방법으로

주로 패키지명, Activity 구조, 권한 정보 등을 추출할 때 사용한다.

매번 아주 친절하게 정보를 제공하는 Android 개발자 사이트에 AAPT2 또한 친절히 설명되어 있다.

여기서 주목할 부분은 바로 덤프 (dump) 다.

 

 

AAPT2  |  Android 개발자  |  Android Developers

AAPT2(Android Asset Packaging Tool)는 Android 스튜디오 및 Android Gradle 플러그인이 앱의 리소스를 컴파일하고 패키징하는 데 사용하는 빌드 도구입니다. AAPT2는 리소스를 Android 플랫폼에 최적화된 바이너리

developer.android.com

우선 AAPT 명령어를 사용하기 위해선 Android Studio의 SDK가 설치 된 상태여야 한다.

(버전 상관없이) SDK 설치가 됐다면 다음과 같은 순서로 진행한다.


추출 방법

1. aapt.exe 파일의 디렉토리 복사
보통 C:\Users\윈도우사용자명\AppData\Local\Android\Sdk\build-tools\SDK버전 인 경우가 많다.
※ 필자의 경우 SDK 30 버전이지만, 어떤 버전이든 무관하다.

2. 명령 프롬프트 - 복사해 둔 aapt 디렉토리로 이동 (cd 디렉토리)

Microsoft Windows [Version 10.0.18362.1082]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\UserName> cd C:\Users\UserName\AppData\Local\Android\Sdk\build-tools\SDK버전

--------------실행 결과--------------
C:\Users\UserName\AppData\Local\Android\Sdk\build-tools\SDK버전>

 

3. 매니패스트 정보를 조회할 apk 디렉토리 복사

4. 명령 프롬프트 - aapt dump badging 디렉토리 명령어 실행

aapt dump badging C:\Users\UserName\Desktop\Test\apk\test.apk

-------------- 실행결과 (apk 크기에 따라 굉장히 방대한 양이 나올 수 있으나, 주요 정보만 표기) --------------

package: name='패키지명' versionCode='■■' versionName='■.■.■' compileSdkVersion='■■' compileSdkVersionCodename='■■'
sdkVersion:'■■'
targetSdkVersion:'■■'
uses-permission: name='권한 정보'
launchable-activity: name='기본 액티비티명'

 

(+)

전체적인 매니패스트 구조를 보려면 다음 명령어를 실행하면 모든 Activity 정보를 확인할 수 있다.

aapt dump xmltree C:\Users\UserName\Desktop\Test\apk\test.apk AndroidManifest.xml

 

※ LG VELVET 단말 기준으로 단말마다 화면/메뉴 이름이 다를 수 있음

 

타이틀개발자 옵션 ON

1. 연결할 단말의 '설정 앱 - 시스템 - 휴대폰 정보 - 소프트웨어 정보' 화면으로 이동한다.
2. '빌드 번호'를 7회 터치​한다. (친절하게 '개발자가 되려면 N단계 남았습니다.' 라는 토스트가 표시 된다.)
3. '개발자가 되셨습니다.' 토스트가 표시 되면 완료
4. 시스템 화면으로 이동하면 '개발자 옵션' 메뉴가 생성 된다.

 

 

타이틀USB 디버깅 ON

1. 스마트폰을 USB 연결선을 이용하여 PC와 연결한다.
2. USB 연결 옵션을 '사진 및 동영상 전송(PTP)'로 설정한다. ★★★ '파일 전송(MTP)'이면 안되는 경우 ! 
3. 연결한 단말의 '설정 앱 - 시스템 - 개발자 옵션' 화면으로 이동한다.
4. 'USB 디버깅' ON
5. 'USB 디버깅을 허용하시겠습니까?' 다이얼로그창에서 '허용' 
6. PC에 연결한 컴퓨터 RSA 키 지문이 적힌 다이얼로그창에서 '허용'

 

연결 확인

1. 명령 프롬프트 실행 (윈도우 키 + X)
2. adb devices 명령어 입력
3. 연결한 단말의 UDID 옆에 device가 뜨면 성공 !
※ unauthorized 로 뜨면 연결된 PC가 허용되지 않은 상태

C:\Users\UserName>adb devices
List of devices attached
LMG900Nd2e163d1 unauthorized // USB 디버깅 연결 비허용
LMG900Nd2e163d1 device // USB 디버깅 연결 허용

 

테스트 자동화 프로그램을 구축하면서 많이 쓰는 adb 명령어 !

adb 명령어를 사용하려면 환경변수 설정을 해줘야 한다.


사전준비

1. Android Studio SDK 설치

 

Download Android Studio and SDK tools  |  Android 스튜디오

developer.android.com

2. adb.exe 파일의 디렉토리 파악

보통 C:\Users\윈도우사용자명\AppData\Local\Android\Sdk\platform-tools 인 경우가 많다.

adb.exe 파일이 있는 platform-tools 의 디렉토리 파악

환경변수 설정

1. 윈도우 탐색창에서 제어판 - 시스템 환경 변수 편집 실행

 

탐색창에 '환경' 검색 후 실행

 

3. 시스템 속성 - 환경 변수 - Path - 편집 - 새로 만들기 - platform-tools 경로 추가

 

하는 김에 Sdk\emulator 와 Sdk\tools 도 하면 유용하다.

4. 명령 프롬프트 - adb 명령어 수행 결과 확인

 

실행 (윈도우 키 + R) - cmd 입력 (명령 프롬프트 실행) - adb

 

PC에 연결된 디바이스 확인하기

명령 프롬프트 - adb devices 명령어 입력

C:\Users\UserName> adb devices
List of devices attached
LMG900Nd2e163d1 device
emulator-5554   device

실제단말(LMG900Nd2e163d1), 애뮬레이터(emulator-5554) 모두 표시 된다.

명령어 수행 결과에는 USB 디버그 모드가 켜진 단말만 표시 된다.

+ Recent posts