Ir para o console

Teste de loop de jogo do Firebase Test Lab

A automatização dos testes de jogos é desafiadora devido à ampla variedade de bibliotecas de IU usadas para o desenvolvimento de jogos (sendo algumas dependentes de mecanismo) e às dificuldades de automatizar a navegação na IU dos jogos. Para a execução de testes de app para jogos, o Test Lab agora inclui compatibilidade para um "modo de demonstração" para executar o jogo enquanto as ações de um jogador são simuladas. Esse modo pode incluir vários loops ou cenários. Eles são organizados de maneira lógica com marcadores, para que os loops relacionados possam ser executados juntos.

Neste documento, você encontra as diretrizes da implementação de loops de jogos. Dessa maneira, fica fácil usá-los para testar seu jogo durante o desenvolvimento. Elas se aplicam aos testes executados em um único dispositivo de teste, em um conjunto de dispositivos ou com o Test Lab.

Primeiros passos

Para usar o teste de loop de jogo no Test Lab, seu jogo precisa ser modificado para executar as seguinte ações:

  1. Iniciar o loop.
  2. Executar o loop.
  3. (Opcional) Fechar o app do jogo.

Iniciar o loop

Ao iniciar o teste de loop, o jogo é acionado com um intent específico. Você precisa modificar o manifesto e adicionar um novo filtro de intent à sua atividade, conforme mostrado no seguinte código de exemplo:

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

Executar o loop

A atividade, quando iniciada, verifica qual intent a acionou, como mostrado no seguinte exemplo de código:

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
}

Kotlin

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
}

Recomendamos que esse código seja executado no método onCreate, mas isso pode ser feito posteriormente, por exemplo, após o carregamento inicial do mecanismo do jogo. Em seguida, dependendo do mecanismo do jogo, implemente o loop de jogo (ou os vários loops, conforme descrito em Múltiplos loops de jogo). A seguir, exemplos de como esses loops podem ser usados:

  • Executar uma fase do jogo como se ele fosse jogado por um usuário final. Crie um script com a entrada do usuário, deixe o usuário inativo ou coloque inteligência artificial (IA) no lugar do usuário, se isso fizer sentido no seu jogo. Por exemplo, em um jogo de corrida de carros, é fácil usar um motorista de IA para a entrada do usuário se ela já estiver implementada.
  • Executar o jogo na configuração de qualidade mais alta para ver se os dispositivos são compatíveis com ela.
  • Executar um teste técnico que inclua a compilação de múltiplos sombreadores, a execução deles, a verificação da saída esperada etc.

Test Loop Manager

Para ajudá-lo a integrar loops de jogos, bem como auxiliar você e a equipe do Controle de qualidade a executá-los nos dispositivos locais, fornecemos um app de código aberto chamado Test Loop Manager.

Para usar o Test Loop Manager, faça o seguinte:

  1. Faça o download do Test Loop Manager.
  2. Instale o Test Loop Manager no seu dispositivo com o seguinte comando:
    adb install testloopmanager.apk
  3. Abra o aplicativo chamado "Test Loop Apps" no seu smartphone ou tablet. Uma lista de apps é exibida no dispositivo que contém os loops de jogo. Caso seu app não esteja na lista, verifique se o filtro de intent corresponde ao descrito em Iniciar o loop.
  4. Selecione o app do jogo que você quer testar.
    1. Depois de clicar no nome do app do jogo na lista, selecione os cenários ou loops implementados nele. Se há vários loops implementados, consulte Múltiplos loops de jogo.
    2. Clique em Executar teste e assista enquanto os cenários estiverem sendo executados.

Fechar o app do jogo

No final do teste, feche o app usando:

Java

yourActivity.finish();

Kotlin

yourActivity.finish()

Geralmente, o teste de loop de jogo permite que a biblioteca de IU inicie o próximo loop. Quando você não fecha o app, a biblioteca da interface do usuário que executa os loops não sabe que o teste terminou e o app pode ser encerrado depois de algum tempo.

Executar o teste de loop de jogo no Test Lab

Acesse o Console do Firebase e crie um projeto, caso ainda não tenha um. Para usar o Test Lab sem custo, mas com cota diária limitada, use o plano de faturamento Spark. Para saber mais sobre o uso do Test Lab sem limites de uso, consulte Planos de preços do Firebase.

Usar o Console do Firebase

Use as seguintes instruções para executar um teste de loop de jogo com o Console do Firebase:

  1. No Console do Firebase, clique em Test Lab no painel de navegação à esquerda.
  2. Clique em Execute seu primeiro teste ou em Executar um teste, se ele já foi 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.
  5. (opcional) Para selecionar um subconjunto de cenários ou loops de jogo disponíveis, siga um destes procedimentos:

    • Para escolher cenários específicos, insira uma lista ou um intervalo de números de cenários no campo Cenários.
    • Para escolher todos os cenários que tenham um rótulo específico aplicado, insira um ou mais rótulos de cenários no campo Rótulos.
  6. Clique em Continuar.

  7. Selecione os dispositivos físicos a serem usados para testar seu app.

  8. Clique em Iniciar testes.

Para mais informações sobre o Console do Firebase, consulte Como usar o Firebase Test Lab no Console do Firebase.

Usar o ambiente de linha de comando gcloud

Siga estas instruções para executar um teste de loop de jogo com a interface de linha de comando (CLI, na sigla em inglês) do gcloud:

  1. Se você ainda não tem um projeto do Firebase, acesse o Console do Firebase e clique em Adicionar projeto para criar um.
  2. Instale o Google Cloud SDK, que inclui a CLI do gcloud.
  3. Faça login usando uma Conta do Google:

    gcloud auth login

  4. Defina o projeto do Firebase com o comando a seguir, em que PROJECT_ID é o projeto criado na etapa 1:

    gcloud config set project PROJECT_ID
    
  5. Execute o primeiro teste da seguinte maneira:

    gcloud firebase test android run \
    --type=game-loop --app=<path-to-apk> \
    --device model=herolte,version=23
  6. (opcional) Para selecionar um subconjunto de cenários ou loops de jogo disponíveis, siga um destes procedimentos ao executar um teste de loop:

    • Para escolher cenários específicos, defina uma lista de números de cenários usando a sinalização --scenario-numbers, por exemplo, --scenario-numbers=1,3,5.
    • Para escolher todos os cenários que tenham um rótulo específico aplicado, defina um ou mais rótulos usando a sinalização --scenario-labels, por exemplo, --scenario-labels=performance,gpu.

Para mais informações sobre como usar a CLI do gcloud com o Test Lab, consulte Como usar o Firebase Test Lab para Android com a linha de comando do gcloud.

Recursos opcionais

Nesta seção, você aprende a usar recursos opcionais, como gravar dados em um arquivo de saída, usar múltiplos loops de jogos e marcar loops relacionados para que eles possam ser testados facilmente em uma única matriz de teste.

Gravar dados de saída

No loop de jogo, a saída pode ser gravada em um arquivo fornecido com o método launchIntent.getData(). Esses dados de saída podem ser exibidos pelo Test Lab na página de resultados do teste. Para ver um exemplo de um arquivo de saída, consulte Exemplo do arquivo de saída do teste de loop de jogo.

O Test Lab segue as práticas recomendadas para compartilhar um arquivo entre os apps, descritas em Como compartilhar um arquivo. No método onCreate() da atividade, em que você recebe o intent, também é possível verificar esse arquivo usando o seguinte código:

Java

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

Kotlin

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

Se você quiser gravar esse arquivo a partir do código C++ do app de jogo, passe o descritor do arquivo em vez do caminho dele, conforme mostrado no seguinte código de exemplo:

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

Kotlin

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

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

Múltiplos loops de jogo

Também é possível usar múltiplos loops no app de jogo. Por exemplo, se o 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 uma falha do app ocorre no nível 32, você inicia esse loop de jogo diretamente para tentar reproduzi-la e testar as correções do bug.

Por exemplo, se há cinco loops de jogo no app, só é preciso adicionar uma linha ao arquivo de manifesto do app, dentro do elemento <application>:

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

Em seguida, ao iniciar o Test Loop Manager, use uma tela de seleção para escolher o loop a ser iniciado. Quando você escolhe múltiplos loops, cada loop é iniciado em sequência depois que o anterior é concluído. Com o Test Lab, você seleciona quais loops quer iniciar.

O intent de inicialização contém o loop de destino, que é um parâmetro inteiro, no intervalo de 1 até o número máximo de loops suportados.

Leia os dados adicionais do intent no Java, conforme mostrado no seguinte código de exemplo:

Java

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

Kotlin

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

Em seguida, passe esses dados extras ao código C++ nativo para alterar o comportamento do loop com base no valor int resultante.

Marcar loops de jogo

Para facilitar o trabalho da equipe de controle de qualidade ao iniciar um conjunto de loops de jogos relacionados, você pode marcar o loop com um ou mais rótulos de cenário, por exemplo, "todos os loops de compatibilidade do jogo". Para isso, é preciso adicionar uma linha de metadados ao arquivo de manifesto do app, semelhante ao seguinte exemplo:

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

Crie os próprios rótulos ou use os quatro rótulos predefinidos que fornecemos:

  • com.google.test.loops.player_experience: loops usados para reproduzir a experiência de um usuário real no 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: 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: loops usados para testar uma ampla variedade de problemas de compatibilidade, incluindo problemas de E/S e de OpenSSL.
  • com.google.test.loops.performance: 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.

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

O teste de loop de jogo no Test Lab tem os seguintes problemas conhecidos:

  • Compatibilidade limitada com a API Khronos Vulkan. Somente os seguintes dispositivos disponíveis no Test Lab são compatíveis com a API Vulkan: Samsung Galaxy S7 (API nível 24) e Google Pixel (API nível 25).
  • Algumas falhas não são compatíveis com backtraces. Por exemplo, em algumas versões, a saída do processo debuggerd pode ser suprimida usando prctl(PR_SET_DUMPABLE, 0). Para saber mais, consulte debuggerd.
  • A API nível 19 não é compatível atualmente devido a erros de permissão de arquivo.

Exemplo de arquivo de saída de teste de loop de jogo

Use arquivos de dados de saída formatados como mostrado no exemplo abaixo para exibir os resultados do teste de loop do jogo no Console do Firebase. As áreas com /.../ podem conter campos personalizados, desde que não entrem em conflito com os nomes de outros campos usados no 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
}