Primeiros passos com testes de loop de jogo

Pode ser difícil automatizar testes de jogos quando esses apps são criados em diferentes frameworks de interface. Os testes de loop de jogo permitem que você integre seus testes nativos com o Test Lab e os execute facilmente nos dispositivos selecionados. Um teste de loop de jogo executa seu teste usando o app de jogos e simula as ações de um jogador real. Veja neste guia como executar um teste de loop de jogo e, em seguida, visualizar e gerenciar os resultados no Console do Firebase.

Dependendo do mecanismo do jogo, é possível implementar testes com um ou vários loops. Um loop é uma execução completa ou parcial do teste no seu app de jogos. Os loops de jogo podem ser usados para:

  • Executar uma fase do jogo como se ela fosse jogada por um usuário final. É possível criar um script com a entrada do usuário, deixar o usuário inativo ou substituí-lo por uma IA se fizer sentido no seu jogo (por exemplo, digamos que você tenha um app de jogo de corrida e já tenha uma IA implementada). É fácil usar um piloto de IA para simular um usuário.
  • Executar o jogo na configuração de qualidade mais alta para ver se os dispositivos vão ter problemas de desempenho.
  • Executar um teste técnico que inclua a compilação de vários sombreadores, a execução deles, a verificação da saída esperada etc.

É possível executar um teste de loop de jogo em um único dispositivo de teste, em um conjunto de dispositivos de teste ou no Test Lab. No entanto, não recomendamos a execução de testes de loop de jogo em dispositivos virtuais porque eles têm taxas de quadros gráficos menores do que as dos dispositivos físicos.

Antes de começar

Para implementar um teste, você precisa primeiro configurar seu app para testes de loop de jogo.

  1. No manifesto do app, adicione um novo filtro de intent à sua atividade:

    <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>
    

    Isso permite que o Test Lab inicie seu jogo acionando-o com uma intent específica.

  2. No seu código (recomendamos na declaração do método onCreate), adicione o seguinte:

    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
    }

    Isso permite que sua atividade verifique a intent que a inicia. Também é possível adicionar esse código mais tarde, se preferir (por exemplo, depois de carregar inicialmente o mecanismo do jogo).

  3. Recomendado: no final do teste, adicione:

    Kotlin+KTX

    yourActivity.finish()

    Java

    yourActivity.finish();

    Isso fecha seu app quando o teste de loop de jogo é concluído. O teste depende do framework da interface do seu app para iniciar o próximo loop. O fechamento do app informa que o teste foi concluído.

Criar e executar um teste de loop de jogo

Depois de configurar o app para testes de loop de jogo, é possível criar um teste e executá-lo imediatamente no app de jogos. O teste no Test Lab pode ser feito usando o Console do Firebase, a interface de linha de comando (CLI) gcloud ou o Test Loop Manager em um dispositivo local.

Executar em um dispositivo local

O Test Loop Manager do Test Lab é um app de código aberto que ajuda a integrar testes de loop de jogo e a executá-los nos seus dispositivos locais. Ele também permite que sua equipe de controle de qualidade execute os mesmos loops de jogo nos dispositivos.

Para executar um teste em um dispositivo local usando o Test Loop Manager, siga estas etapas:

  1. Faça o download do Test Loop Manager em um smartphone ou tablet e instale-o executando:
    adb install testloopmanager.apk
  2. No seu smartphone ou tablet, abra o app Test Loop Apps. O app exibe uma lista de aplicativos no seu dispositivo que podem ser executados com loops de jogo. Caso o app de jogos não esteja aparecendo aqui, verifique se o filtro de intent corresponde ao descrito na primeira etapa da seção Antes de começar.
  3. Selecione seu app de jogos e o número de loops que você quer executar. Observação: nesta etapa, é possível executar um subconjunto de loops em vez de apenas um loop. Para mais informações sobre como executar vários loops de uma só vez, consulte Recursos opcionais.
  4. Clique em Executar teste. Seu teste começa a ser executado imediatamente.

Executar no Test Lab

É possível executar um teste de loop de jogo no Test Lab usando o Console do Firebase ou a CLI gcloud. Antes de começar, abra o Console do Firebase e crie um projeto.

Usar o Console do Firebase

  1. No Console do Firebase, clique em Test Lab no painel à esquerda.
  2. Clique em Execute seu primeiro teste ou em Executar um teste, se ele já tiver sido executado no seu projeto.
  3. Selecione Loop de jogo como tipo de teste e clique em Continuar.
  4. Clique em Procurar e navegue até o arquivo .apk do app. Observação: nesta etapa, é possível executar um subconjunto de loops em vez de apenas um loop. Para mais informações sobre como executar vários loops de uma só vez, consulte Recursos opcionais.
  5. Clique em Continuar.
  6. Selecione os dispositivos físicos a serem usados para testar seu app.
  7. Clique em Iniciar testes.

Para mais informações sobre como começar a usar o Console do Firebase, consulte Primeiros passos com o Console do Firebase.

Usar a linha de comando (CLI) gcloud

  1. Faça o download e instale o SDK do Google Cloud, caso ainda não tenha feito isso.

  2. Faça login na CLI gcloud usando sua Conta do Google:

    gcloud auth login

  3. Defina seu projeto do Firebase na gcloud, em que PROJECT_ID é o ID do projeto do Firebase:

    gcloud config set project PROJECT_ID
    
  4. Execute seu primeiro teste:

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

Para saber mais sobre como começar a usar a CLI gcloud, consulte Primeiros passos com a linha de comando gcloud.

Recursos opcionais

O Test Lab oferece vários recursos opcionais que permitem personalizar ainda mais seus testes, incluindo a capacidade de gravar dados de saída, suporte a vários loops de jogo e rótulos para loops relacionados.

Gravar dados de saída

Seu teste de loop de jogo pode gravar a saída em um arquivo especificado no método launchIntent.getData(). Depois de executar um teste, é possível acessar esses dados de saída na seção Test Lab do Console do Firebase. Consulte o exemplo de arquivo de saída de teste do loop de jogo.

O Test Lab segue as práticas recomendadas para compartilhar um arquivo entre apps descritas em Como compartilhar um arquivo. No método onCreate() da atividade, que é onde a intent está localizada, é possível verificar o arquivo de saída de dados executando o seguinte código:

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());
    // ...
}

Se você quiser gravar no arquivo do lado do C++ do seu app de jogo, transmita o descritor do arquivo em vez do caminho do arquivo:

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);
}

Exemplo de arquivo de saída

Use os arquivos de dados de saída (formatados como no exemplo abaixo) para exibir os resultados do teste de loop de jogo na seção Test Lab do Console do Firebase. As áreas mostradas como /.../ podem conter todos os campos personalizados necessários, desde que não entrem em conflito com os nomes de outros campos usados neste arquivo:

{
  "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
}

Múltiplos loops de jogo

Pode ser útil executar vários loops de jogo no seu app. Um loop é uma execução completa do seu app de jogo do início ao fim. Por exemplo, se seu jogo tem vários níveis, é possível ter um loop de jogo para iniciar cada um deles em vez de ter somente um que itere por todos eles. Dessa forma, se o app falhar no nível 32, será possível iniciar esse loop de jogo diretamente para reproduzir a falha e testar as correções de bugs.

Para permitir que seu app execute vários loops de uma só vez:

  • Se você estiver executando um teste com o Test Loop Manager:

    1. Adicione a seguinte linha ao manifesto do app, dentro do elemento <application>:

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

      Essa intent de inicialização contém o loop de destino como um parâmetro inteiro. No campo android:value, é possível especificar um número inteiro de 1 a 1.024 (o número máximo de loops permitidos para um único teste). Observe que os loops são indexados a partir de 1, e não de 0.

    2. No app Test Loop Manager, é exibida uma tela de seleção que permite selecionar quais loops você quer executar. Se você selecionar vários loops, cada loop será iniciado em sequência após a conclusão do loop anterior.

  • Se estiver executando um teste com o Console do Firebase, insira uma lista ou um intervalo de números de loop no campo Cenários.

  • Se estiver executando um teste com a CLI gcloud, especifique uma lista de números de loop usando a sinalização --scenario-numbers. Por exemplo, --scenario-numbers=1,3,5 executa loops 1, 3 e 5.

  • Se você estiver escrevendo em C++ e quiser alterar o comportamento do seu loop, transmita o seguinte extra para o código C++ nativo:

    Kotlin+KTX

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

    Java

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

    Agora é possível alterar o comportamento do seu loop com base no valor int resultante.

Rotular loops de jogo

Ao rotular seus loops de jogo com um ou mais rótulos de cenário, você e sua equipe de controle de qualidade podem lançar facilmente um conjunto de loops de jogo relacionados (por exemplo, "todos os loops de jogo de compatibilidade") e testá-los em uma única matriz. É possível criar seus próprios rótulos ou usar os predefinidos oferecidos pelo Test Lab:

  • com.google.test.loops.player_experience: para loops usados para reproduzir a experiência de um usuário real ao jogar o jogo. O objetivo do teste com esses loops é localizar os problemas que o usuário encontraria durante o jogo.
  • com.google.test.loops.gpu_compatibility: para loops usados para testar problemas relacionados à GPU. O objetivo do teste com esses loops é executar o código GPU que poderia não ser executado corretamente na produção e, dessa forma, evidenciar problemas com hardware e drivers.
  • com.google.test.loops.compatibility: para loops usados para testar uma ampla variedade de problemas de compatibilidade, incluindo problemas de E/S e de OpenSSL.
  • com.google.test.loops.performance: para loops usados para testar o desempenho do dispositivo. Por exemplo, um jogo pode ser executado com as configurações mais complexas de gráfico para ver como um novo dispositivo se comporta.

Para permitir que seu app execute loops com o mesmo rótulo:

  • Se você estiver executando um teste com o Test Loop Manager:

    1. No manifesto do seu app, adicione a seguinte linha de metadados e substitua LABEL_NAME por um rótulo de sua escolha:

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

      No campo android:value, é possível especificar um intervalo ou um conjunto de números inteiros de 1 a 1.024 (o número máximo de loops permitidos para um único teste) que representam os loops que você quer rotular. Observe que os loops são indexados começando de 1, e não de 0. Por exemplo, android:value="1,3-5" aplica LABEL_NAME aos loops 1, 3, 4 e 5.

    2. No app Test Loop Manager, insira um ou mais rótulos no campo Rótulos.

  • Se você estiver executando um teste com o Console do Firebase, insira um ou mais rótulos no campo Rótulos.

  • Se você estiver executando um teste com a CLI gcloud, especifique um ou mais rótulos de cenário usando a sinalização --scenario-labels (por exemplo, --scenario-labels=performance,gpu).

Suporte ao licenciamento de apps

O Test Lab é compatível com apps que usam o serviço de licenciamento de apps oferecido pelo Google Play. Para verificar o licenciamento ao testar seu app com o Test Lab, publique o app no canal de produção na Play Store. Para testar seu app no Canal Beta ou Alfa usando o Test Lab, remova a verificação de licenciamento antes de fazer o upload do seu app no Test Lab.

Problemas conhecidos

Os testes de loop de jogo no Test Lab têm os seguintes problemas conhecidos:

  • Algumas falhas não são compatíveis com backtraces. Por exemplo, em alguns builds de lançamento, a saída do processo debuggerd pode ser suprimida usando prctl(PR_SET_DUMPABLE, 0). Para saber mais, veja debuggerd.
  • A API nível 19 não é compatível atualmente devido a erros de permissão de arquivo.