게임 루프 테스트 시작하기

게임 앱이 다른 UI 프레임워크를 기반으로 빌드되면 게임 테스트를 자동화하기 어려울 수 있습니다. 게임 루프 테스트를 사용하면 기본 테스트를 Test Lab과 통합하여 선택한 기기에서 쉽게 실행할 수 있습니다. 게임 루프 테스트는 실제 플레이어의 행동을 시뮬레이션하면서 게임 앱을 통해 테스트를 실행합니다. 이 가이드에서는 게임 루프 테스트를 실행한 후 Firebase Console에서 테스트 결과를 보고 관리하는 방법을 설명합니다.

게임 엔진에 따라 단일 또는 여러 루프로 테스트를 구현할 수 있습니다. 루프는 게임 앱에서 테스트를 전체 또는 부분 실행합니다. 게임 루프를 사용하여 다음을 수행할 수 있습니다.

  • 최종 사용자가 플레이하는 것과 같은 방식으로 게임 레벨을 진행합니다. 사용자 입력을 스크립트로 작성하거나, 사용자를 자리 비움 상태로 두거나, 게임에 따라서는 사용자를 AI로 대체할 수 있습니다. 예를 들어 이미 구현된 AI가 존재하는 자동차 경주 게임 앱의 경우 사용자 입력을 AI 운전자로 손쉽게 대체할 수 있습니다.
  • 최고 품질 설정으로 게임을 실행하여 기기에서 해당 설정을 지원하는지 확인합니다.
  • 여러 셰이더를 컴파일하고, 실행하고, 예상대로 출력되는지 확인하는 등 기술 테스트를 실행합니다.

단일 테스트 기기, 일련의 테스트 기기 또는 Test Lab에서 게임 루프 테스트를 실행할 수 있습니다. 하지만 가상 기기의 게임 루프 테스트는 실제 기기보다 그래픽 프레임 속도가 느리므로 실행하지 않는 것이 좋습니다.

시작하기 전에

테스트를 구현하려면 먼저 게임 루프 테스트를 하도록 앱을 구성해야 합니다.

  1. 앱 매니페스트에서 활동에 새 인텐트 필터를 추가합니다.

    <activity android:name=".MyActivity">
       <intent-filter>
           <action android:name="com.google.intent.action.TEST_LOOP"/>
           <category android:name="android.intent.category.DEFAULT"/>
           <data android:mimeType="application/javascript"/>
       </intent-filter>
       <intent-filter>
          ... (other intent filters here)
       </intent-filter>
    </activity>

    그러면 Test Lab에서 특정 인텐트로 게임을 트리거할 수 있습니다.

  2. 코드(onCreate 메서드 선언 내부에 권장)에서 다음을 추가합니다.

    Kotlin+KTX

    val launchIntent = intent
    if (launchIntent.action == "com.google.intent.action.TEST_LOOP") {
        val scenario = launchIntent.getIntExtra("scenario", 0)
        // Code to handle your game loop here
    }

    Java

    Intent launchIntent = getIntent();
    if(launchIntent.getAction().equals("com.google.intent.action.TEST_LOOP")) {
        int scenario = launchIntent.getIntExtra("scenario", 0);
        // Code to handle your game loop here
    }

    이렇게 하면 활동이 이를 실행하는 인텐트를 확인할 수 있습니다. 원하는 경우 나중에 이 코드를 추가할 수도 있습니다. 예를 들어 게임 엔진을 처음 로드한 후에 추가할 수 있습니다.

  3. 권장 사항: 테스트가 끝나면 다음을 추가합니다.

    Kotlin+KTX

    yourActivity.finish()

    Java

    yourActivity.finish();

    게임 루프 테스트가 완료되면 앱이 종료됩니다. 테스트는 앱의 UI 프레임워크를 사용하여 다음 루프를 시작하고 앱을 종료하면 테스트가 완료되었음을 알립니다.

게임 루프 테스트 생성 및 실행

게임 루프 테스트용 앱을 구성한 후 게임 앱에서 즉시 테스트를 만들고 실행할 수 있습니다. Firebase Console 또는 gcloud 명령줄 인터페이스(CLI)를 사용하여 Test Lab에서 테스트를 실행하거나 Test Loop Manager를 사용하여 로컬 기기에서 테스트를 실행할 수 있습니다.

로컬 기기에서 실행

Test LabTest Loop Manager는 게임 루프 테스트를 통합하고 로컬 기기에서 실행할 수 있도록 도와주는 오픈소스 앱입니다. 또한 품질보증팀이 기기에서 동일한 게임 루프를 실행할 수 있습니다.

Test Loop Manager를 사용하여 로컬 기기에서 테스트를 실행하려면 다음 안내를 따르세요.

  1. 스마트폰 또는 태블릿에 Test Loop Manager를 다운로드하고 다음을 실행하여 설치합니다.
    adb install testloopmanager.apk
  2. 스마트폰 또는 태블릿에서 테스트 루프 앱을 엽니다. 앱은 게임 루프로 실행할 수 있는 앱 목록을 기기에 표시합니다. 게임 앱이 표시되지 않으면 인텐트 필터가 시작하기 전에 섹션의 첫 번째 단계에서 설명하는 필터와 일치하는지 확인하세요.
  3. 게임 앱을 선택한 다음 실행할 루프 수를 선택합니다. 참고: 이 단계에서는 하나의 루프 대신 루프 하위 집합을 실행할 수 있습니다. 한 번에 여러 루프를 실행하는 방법에 대한 자세한 내용은 선택적 기능을 참조하세요.
  4. 테스트 실행을 클릭합니다. 테스트가 즉시 시작됩니다.

Test Lab에서 실행

Firebase Console 또는 gcloud CLI를 사용하여 Test Lab에서 게임 루프 테스트를 실행할 수 있습니다. 시작하기 전에 아직 만들지 않았다면 Firebase Console을 열고 프로젝트를 만듭니다.

Firebase Console 사용

  1. Firebase Console의 왼쪽 패널에서 Test Lab을 클릭합니다.
  2. 첫 번째 테스트 실행(또는 프로젝트에서 이전에 테스트를 실행한 적이 있다면 테스트 실행)을 클릭합니다.
  3. 테스트 유형으로 게임 루프를 선택하고 계속을 클릭합니다.
  4. 찾아보기를 클릭한 후 앱의 .apk 파일을 찾습니다. 참고: 이 단계에서는 하나의 루프 대신 루프 하위 집합을 실행할 수 있습니다. 한 번에 여러 루프를 실행하는 방법에 대한 자세한 내용은 선택적 기능을 참조하세요.
  5. 계속을 클릭합니다.
  6. 앱을 테스트하는 데 사용할 실제 기기를 선택합니다.
  7. 테스트 시작을 클릭합니다.

Firebase Console을 시작하는 방법에 대한 자세한 내용은 Firebase Console로 테스트 시작을 참조하세요.

gcloud 명령줄(CLI) 사용

  1. 아직 설치하지 않았다면 Google Cloud SDK를 다운로드하여 설치합니다.

  2. Google 계정으로 gcloud CLI에 로그인합니다.

    gcloud auth login

  3. gcloud에서 Firebase 프로젝트를 설정합니다. 여기서 PROJECT_ID는 Firebase 프로젝트의 ID입니다.

    gcloud config set project PROJECT_ID
    
  4. 첫 번째 테스트 실행:

    gcloud firebase test android run \
     --type=game-loop --app=<var>path-to-apk</var> \
     --device model=herolte,version=23
    

gcloud CLI 시작에 대한 자세한 내용은 gcloud 명령줄로 테스트 시작을 참조하세요.

선택적 기능

Test Lab은 출력 데이터 쓰기 기능, 여러 게임 루프 지원, 관련 루프 라벨 등 테스트를 추가로 맞춤설정할 수 있는 몇 가지 선택적 기능을 제공합니다.

출력 데이터 쓰기

게임 루프 테스트는 launchIntent.getData() 메서드에 지정된 파일에 출력을 쓸 수 있습니다. 테스트를 실행한 후 Firebase Console의 Test Lab 섹션에서 이 출력 데이터에 액세스할 수 있습니다(게임 루프 테스트 출력 파일 예시 참조).

Test Lab파일 공유에 설명된 앱 간 파일 공유에 대한 권장사항을 따릅니다. 인텐트가 위치한 활동의 onCreate() 메서드에서 다음 코드를 실행하여 데이터 출력 파일을 확인할 수 있습니다.

Kotlin+KTX

val launchIntent = intent
val logFile = launchIntent.data
logFile?.let {
    Log.i(TAG, "Log file ${it.encodedPath}")
    // ...
}

Java

Intent launchIntent = getIntent();
Uri logFile = launchIntent.getData();
if (logFile != null) {
    Log.i(TAG, "Log file " + logFile.getEncodedPath());
    // ...
}

게임 앱의 C++ 부분에서 파일에 쓰려면 파일 경로 대신 파일 설명자를 전달하면 됩니다.

Kotlin+KTX

val launchIntent = intent
val logFile = launchIntent.data
var fd = -1
logFile?.let {
    Log.i(TAG, "Log file ${it.encodedPath}")
    fd = try {
        contentResolver
            .openAssetFileDescriptor(logFile, "w")!!
            .parcelFileDescriptor
            .fd
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
        -1
    } catch (e: NullPointerException) {
        e.printStackTrace()
        -1
    }
}

// C++ code invoked here.
// native_function(fd);

Java

Intent launchIntent = getIntent();
Uri logFile = launchIntent.getData();
int fd = -1;
if (logFile != null) {
    Log.i(TAG, "Log file " + logFile.getEncodedPath());
    try {
        fd = getContentResolver()
                .openAssetFileDescriptor(logFile, "w")
                .getParcelFileDescriptor()
                .getFd();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        fd = -1;
    } catch (NullPointerException e) {
        e.printStackTrace();
        fd = -1;
    }
}

// C++ code invoked here.
// native_function(fd);

C++

#include <unistd.h>
JNIEXPORT void JNICALL
Java_my_package_name_MyActivity_native_function(JNIEnv *env, jclass type, jint log_file_descriptor) {
// The file descriptor needs to be duplicated.
int my_file_descriptor = dup(log_file_descriptor);
}

출력 파일 예시

Firebase Console의 Test Lab 섹션에 출력 데이터 파일(아래 예시와 같은 형식)을 사용하여 게임 루프 테스트 결과를 표시할 수 있습니다. /.../로 표시된 영역에는 필요한 모든 커스텀 필드가 포함될 수 있지만 이 파일에서 사용된 다른 필드의 이름과 겹치지 않아야 합니다.

{
  "name": "test name",
  "start_timestamp": 0, // Timestamp of the test start (in us).
                           Can be absolute or relative
  "driver_info": "...",
  "frame_stats": [
    {
      "timestamp": 1200000, // Timestamp at which this section was written
                               It contains value regarding the period
                               start_timestamp(0) -> this timestamp (1200000 us)
      "avg_frame_time": 15320, // Average time to render a frame in ns
      "nb_swap": 52, // Number of frame rendered
      "threads": [
        {
          "name": "physics",
          "Avg_time": 8030 // Average time spent in this thread per frame in us
        },
        {
          "name": "AI",
          "Avg_time": 2030 // Average time spent in this thread per frame in us
        }
      ],
      /.../ // Any custom field you want (vertices display on the screen, nb units …)
    },
    {
      // Next frame data here, same format as above
    }
  ],
  "loading_stats": [
    {
      "name": "assets_level_1",
      "total_time": 7850, // in us
      /.../
    },
    {
      "name": "victory_screen",
      "total_time": 554, // in us
      /.../
    }

  ],
  /.../, // You can add custom fields here
}

여러 게임 루프

앱에서 여러 게임 루프를 실행하는 것이 유용할 수 있습니다. 루프는 게임 앱을 처음부터 끝까지 완벽하게 실행합니다. 예를 들어 게임에 여러 레벨이 있는 경우 루프 하나로 모든 레벨을 반복하는 대신 게임 루프를 레벨마다 하나씩 시작할 수 있습니다. 이렇게 하면 레벨 32에서 앱이 비정상 종료된 경우 해당 게임 루프를 바로 실행하여 비정상 종료를 재현하고 버그가 수정되었는지 테스트할 수 있습니다.

앱에서 여러 루프를 한 번에 실행하려면 다음 안내를 따르세요.

  • Test Loop Manager로 테스트를 실행하는 경우:

    1. <application> 요소 내에서 앱의 매니페스트에 다음 줄을 추가합니다.

      <meta-data
        android:name="com.google.test.loops"
        android:value="5" />

      이 시작 인텐트에는 타겟 루프가 정수 매개변수로 포함됩니다. android:value 필드에서 1~1,024 사이의 정수(단일 테스트에 허용되는 최대 루프 수)를 지정할 수 있습니다. 루프의 색인은 0이 아닌 1부터 생성됩니다.

    2. Test Loop Manager 앱에 실행하려는 루프를 선택할 수 있는 선택 화면이 표시됩니다. 여러 루프를 선택하면 이전 루프가 완료된 후 각 루프가 순서대로 하나씩 실행됩니다.

  • Firebase Console에서 테스트를 실행하려면 Scenarios 필드에 루프 번호 목록 또는 범위를 입력합니다.

  • gcloud CLI로 테스트를 실행하려면 --scenario-numbers 플래그를 사용하여 루프 번호 목록을 지정합니다. 예를 들어 --scenario-numbers=1,3,5는 루프 1, 3, 5를 실행합니다.

  • C++를 작성 중이고 루프 동작을 변경하려면 기본 C++ 코드에 다음을 추가로 전달합니다.

    Kotlin+KTX

    val launchIntent = intent
    val scenario = launchIntent.getIntExtra("scenario", 0)

    Java

    Intent launchIntent = getIntent();
    int scenario = launchIntent.getIntExtra("scenario", 0);

    이제 결과 int 값에 따라 루프 동작을 변경할 수 있습니다.

게임 루프 라벨 지정

하나 이상의 시나리오 라벨로 게임 루프에 라벨을 지정하면 QA팀과 함께 서로 관련된 게임 루프(예: '모든 호환성 게임 루프')를 손쉽게 시작하고 단일 매트릭스에서 테스트할 수 있습니다. 라벨을 직접 만들거나 Test Lab에서 제공하는 사전 정의된 라벨을 사용할 수 있습니다.

  • com.google.test.loops.player_experience: 실제 사용자의 게임 플레이 환경을 재현하는 데 사용되는 루프입니다. 이러한 루프로 테스트하는 목적은 실제 사용자가 게임을 플레이하는 중에 발생할 수 있는 문제를 찾기 위함입니다.
  • com.google.test.loops.gpu_compatibility: GPU 관련 문제를 테스트하는 데 사용되는 루프입니다. 이러한 루프로 테스트하는 목적은 프로덕션 환경에서 제대로 실행되지 않을 수 있는 GPU 코드를 실행하여 하드웨어 및 드라이버 관련 문제를 확인하는 것입니다.
  • com.google.test.loops.compatibility: I/O 문제 및 OpenSSL 문제를 포함하여 다양한 호환성 문제를 테스트하는 데 사용되는 루프입니다.
  • com.google.test.loops.performance: 기기 성능을 테스트하는 데 사용되는 루프입니다. 예를 들어 가장 복잡한 그래픽 설정으로 게임을 실행하여 새 기기의 동작을 확인할 수 있습니다.

앱에서 동일한 라벨로 루프를 실행하려면 다음 안내를 따르세요.

  • Test Loop Manager로 테스트를 실행하는 경우:

    1. 앱의 매니페스트에서 다음 메타데이터 줄을 추가하고 LABEL_NAME을 원하는 라벨로 바꿉니다.

      <meta-data
       android:name="com.google.test.loops.LABEL_NAME"
       android:value="1,3-5" />

      android:value 필드에서 라벨을 지정할 루프를 표시하는 1~1,024 사이의 정수 조합이나 범위(단일 테스트에 허용되는 최대 루프 수)를 지정할 수 있습니다. 루프의 색인은 0이 아닌 1부터 생성됩니다. 예를 들어 android:value="1,3-5"LABEL_NAME을 루프 1, 3, 4, 5에 적용합니다.

    2. Test Loop Manager 앱에서 라벨 필드에 하나 이상의 라벨을 입력합니다.

  • Firebase Console에서 테스트를 실행하는 경우 라벨 필드에 하나 이상의 라벨을 입력합니다.

  • gcloud CLI로 테스트를 실행하는 경우 --scenario-labels 플래그를 사용하여 하나 이상의 시나리오 라벨을 지정합니다(예: --scenario-labels=performance,gpu).

앱 라이선스 지원

Test Lab 은 Google Play에서 제공하는 앱 라이선스 서비스를 사용하는 앱을 지원합니다. Test Lab으로 앱을 테스트할 때 라이선스를 정상적으로 확인하려면 Play 스토어의 프로덕션 채널에 앱을 게시해야 합니다. Test Lab으로 알파 또는 베타 채널의 앱을 테스트하려면 Test Lab에 앱을 업로드하기 전에 라이선스 확인을 삭제하세요.

알려진 문제

Test Lab의 게임 루프 테스트에는 다음과 같은 알려진 문제가 있습니다.

  • 일부 오류는 역추적을 지원하지 않습니다. 예를 들어 일부 출시 빌드에서는 prctl(PR_SET_DUMPABLE, 0)을 사용하여 debuggerd 프로세스 출력을 억제할 수 있습니다. 자세한 내용은 debuggerd를 참조하세요.
  • 파일 권한 오류로 인해 현재 API 수준 19는 지원되지 않습니다.