Escalonar seus testes com dispositivos gerenciados pelo Gradle

Os dispositivos gerenciados pelo Gradle melhoram a consistência, o desempenho e a confiabilidade dos testes instrumentados automatizados. Esse recurso, disponível para o nível 27 da API e mais recentes, permite configurar dispositivos de teste físicos remotos ou virtuais nos arquivos Gradle do projeto. O sistema de build usa as configurações para gerenciar totalmente (criar, implantar e excluir) esses dispositivos ao executar seus testes automatizados.

Esse recurso dá visibilidade ao Gradle não apenas para os testes que você está executando, mas também para o ciclo de vida dos dispositivos, melhorando a qualidade da sua experiência de testes porque:

  • Processa problemas relacionados ao dispositivo para garantir que seus testes sejam executados.
  • Usa snapshots do emulador em dispositivos virtuais para melhorar o tempo de inicialização e o uso de memória, além de restaurar dispositivos para um estado limpo entre os testes.
  • Armazena em cache os resultados de testes e executa novamente apenas aqueles que provavelmente vão fornecer resultados diferentes.
  • Oferece um ambiente consistente para executar os testes entre execuções locais e remotas.

Criar um dispositivo virtual gerenciado pelo Gradle

Você pode especificar um dispositivo virtual a ser usado pelo Gradle para testar o app no arquivo de build do módulo. O exemplo de código abaixo cria um Pixel 2 executando o nível 30 da API como um dispositivo gerenciado pelo Gradle.

Kotlin

android {
  testOptions {
    managedDevices {
      localDevices {
        create("pixel2api30") {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // Use only API levels 27 and higher.
          apiLevel = 30
          // To include Google services, use "google".
          systemImageSource = "aosp"
        }
      }
    }
  }
}

Groovy

android {
  testOptions {
    managedDevices {
      localDevices {
        pixel2api30 {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // Use only API levels 27 and higher.
          apiLevel = 30
          // To include Google services, use "google".
          systemImageSource = "aosp"
        }
      }
    }
  }
}

Definir grupos de dispositivos

Para dimensionar os testes em várias configurações de dispositivos, como diferentes níveis de API e formatos, você pode definir inúmeros dispositivos gerenciados pelo Gradle e adicioná-los a um grupo nomeado. O Gradle pode executar os testes em todos os dispositivos do grupo em paralelo.

O exemplo abaixo mostra dois dispositivos adicionados a um grupo chamado phoneAndTablet.

Kotlin

testOptions {
  managedDevices {
    localDevices {
      create("pixel2api29") { ... }
      create("nexus9api30") { ... }
    }
    groups {
      create("phoneAndTablet") {
        targetDevices.add(devices["pixel2api29"])
        targetDevices.add(devices["nexus9api30"])
      }
    }
  }
}

Groovy

testOptions {
  managedDevices {
    localDevices {
      pixel2api29 { ... }
      nexus9api30 { ... }
    }
    groups {
      phoneAndTablet {
        targetDevices.add(devices.pixel2api29)
        targetDevices.add(devices.nexus9api30)
      }
    }
  }
}

Executar testes

Para executar testes usando os dispositivos gerenciados pelo Gradle que você configurou, use o comando abaixo. device-name é o nome do dispositivo que você configurou no script de build do Gradle (como pixel2api30), e BuildVariant é a variante de build do app a ser testado.

No Windows:

gradlew device-nameBuildVariantAndroidTest

No Linux ou no macOS:

./gradlew device-nameBuildVariantAndroidTest

Para executar os testes em um grupo de dispositivos gerenciados pelo Gradle, use os comandos a seguir.

No Windows:

gradlew group-nameGroupBuildVariantAndroidTest

No Linux ou no macOS:

./gradlew group-nameGroupBuildVariantAndroidTest

A saída do teste inclui um caminho para um arquivo HTML que tem o relatório de teste. Você também pode importar os resultados do teste para o Android Studio e fazer uma análise mais detalhada clicando em Run > Test History no ambiente de desenvolvimento integrado.

Ativar a fragmentação de testes

Os dispositivos gerenciados pelo Gradle oferecem suporte à fragmentação de testes, o que permite dividir seu pacote de testes por várias instâncias de dispositivos virtuais idênticas, chamadas fragmentos, que são executadas em paralelo. O uso da fragmentação de testes ajuda a reduzir o tempo geral de execução do teste em detrimento de outros recursos computacionais.

Para definir o número de fragmentos que você quer usar em uma determinada execução de teste, defina o seguinte no arquivo gradle.properties:

android.experimental.androidTest.numManagedDeviceShards=<number_of_shards>

Ao executar os testes usando essa opção, os dispositivos gerenciados pelo Gradle provisionam o número de fragmentos especificado para cada perfil de dispositivo na execução do teste. Por exemplo, se você implantou seus testes em um grupo de três dispositivos e definiu numManagedDeviceShards para dois, os dispositivos gerenciados pelo Gradle vão provisionar um total de seis dispositivos virtuais para a execução do teste.

Quando os testes são concluídos, o Gradle gera os resultados em um arquivo .proto para cada fragmento usado na execução.

Usar dispositivos de teste automatizados

Os dispositivos gerenciados pelo Gradle oferecem suporte a um novo tipo de dispositivo emulador chamado dispositivo de teste automatizado (ATD), que é otimizado para reduzir os recursos de CPU e memória ao executar testes instrumentados. Os ATDs melhoram o desempenho da execução porque:

  • Removem apps pré-instalados que geralmente não são úteis para testar seu app.
  • Desativam determinados serviços em segundo plano que normalmente não são úteis para testar o app.
  • Desativam a renderização de hardware.

Antes de começar, atualize o Android Emulator para a versão mais recente disponível. Em seguida, especifique uma imagem "-atd" ao definir um dispositivo gerenciado pelo Gradle no arquivo de build do módulo, conforme mostrado abaixo.

Kotlin

android {
  testOptions {
    managedDevices {
      localDevices {
        create("pixel2api30") {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // ATDs currently support only API level 30.
          apiLevel = 30
          // You can also specify "google-atd" if you require Google Play Services.
          systemImageSource = "aosp-atd"
        }
      }
    }
  }
}

Groovy

android {
  testOptions {
    managedDevices {
      localDevices {
        pixel2api30 {
          // Use device profiles you typically see in Android Studio.
          device = "Pixel 2"
          // ATDs currently support only API level 30.
          apiLevel = 30
          // You can also specify "google-atd" if you require Google Play Services.
          systemImageSource = "aosp-atd"
        }
      }
    }
  }
}

Você também pode criar grupos de dispositivos da mesma forma que faz com outros dispositivos gerenciados pelo Gradle. Para aproveitar ainda mais as melhorias de desempenho, também é possível usar ATDs com fragmentação de testes para reduzir o tempo total de execução do teste do seu pacote.

O que é removido das imagens ATD?

Além de operar em um modo headless, os ATDs também otimizam a performance removendo ou desativando apps e serviços que normalmente não são necessários para testar o código do seu app. A tabela abaixo oferece uma visão geral dos componentes removidos ou desativados em imagens ATD e descrições dos motivos pelos quais eles podem não ser úteis.

O que é removido nas imagens ATD Por que isso não é necessário ao executar testes automatizados?
Apps de produto do Google:
  • E-mail
  • Maps
  • Chrome
  • Mensagens
  • Play Store e outros
Os testes automatizados precisam se concentrar na lógica do próprio app, supondo que outros apps e a plataforma funcionam corretamente.

Com Espresso-Intents, você pode combinar e validar suas intents de saída ou inclusive disponibilizar respostas de stub em vez de respostas de intent reais.

Apps de Configurações e serviços:
  • CarrierConfig
  • EmergencyInfo
  • OneTimeInitializer
  • PhotoTable (protetores de tela)
  • Provisionamento
  • App Configurações
  • StorageManager
  • Configuração de APN de telefonia
  • WallpaperCropper
  • WallpaperPicker
Esses apps apresentam uma GUI para que os usuários finais mudem as configurações da plataforma, configurem o dispositivo ou gerenciem o armazenamento do dispositivo. Normalmente, isso está fora do escopo dos testes automatizados no nível do app.


Observação: o provedor de configurações ainda está disponível na imagem ATD.

SystemUI Os testes automatizados precisam se concentrar na lógica do próprio app, supondo que outros apps e a plataforma funcionam corretamente.
Apps e serviços do AOSP:
  • Browser2
  • Agenda
  • Camera2
  • Contatos
  • Telefone
  • DeskClock
  • Gallery2
  • LatinIME
  • Launcher3QuickStep
  • Música
  • QuickSearchBox
  • SettingsIntelligence
Normalmente, esses apps e serviços estão fora do escopo dos testes automatizados do código do app.

Usar dispositivos do Firebase Test Lab

É possível executar testes instrumentados automatizados em escala em dispositivos do Firebase Test Lab ao usar dispositivos gerenciados pelo Gradle. Com o Test Lab, é possível executar testes simultaneamente em vários dispositivos Android, tanto físicos quanto virtuais. Eles são executados em data centers remotos do Google. Com o suporte de dispositivos gerenciados pelo Gradle, o sistema de build pode gerenciar totalmente os testes em execução nesses dispositivos do Test Lab com base nas suas configurações.

Começar

As etapas a seguir descrevem como começar a usar dispositivos do Firebase Test Lab com dispositivos gerenciados pelo Gradle. Observe que estas etapas usam a gcloud CLI para fornecer credenciais de usuário, o que pode não se aplicar a todos os ambientes de desenvolvimento. Para mais informações sobre qual processo de autenticação usar para suas necessidades, consulte Como funciona o Application Default Credentials.

  1. Para criar um projeto do Firebase, acesse o console do Firebase. Clique em Adicionar projeto e siga as instruções na tela para criar um projeto. Memorize o ID do projeto.

  2. Para instalar a CLI do Google Cloud, siga as etapas em Instalar a gcloud CLI.

  3. Configure o ambiente local.

    1. Vincule seu projeto do Firebase no gcloud:

      gcloud config set project FIREBASE_PROJECT_ID
      
    2. Autorize o uso das suas credenciais de usuário para acesso à API. Recomendamos que você autorize transmitindo um arquivo JSON da conta de serviço para o Gradle usando a DSL no script de build do módulo:

      Kotlin

      firebaseTestLab {
        ...
        serviceAccountCredentials.set(file(SERVICE_ACCOUNT_JSON_FILE))
      }
      

      Groovy

      firebaseTestLab {
        ...
        serviceAccountCredentials = file(SERVICE_ACCOUNT_JSON_FILE)
      }
      

      Também é possível autorizar manualmente usando o seguinte comando do terminal:

      gcloud auth application-default login
      
    3. Opcional: adicione seu projeto do Firebase como o projeto de cota. Essa etapa só será necessária se você exceder a cota sem custos financeiros do Test Lab.

      gcloud auth application-default set-quota-project FIREBASE_PROJECT_ID
      
  4. Ative as APIs obrigatórias.

    Na página da biblioteca de APIs do Console para desenvolvedores do Google, ative a API Cloud Testing e a API Cloud Tool Results digitando esses nomes na caixa de pesquisa na parte de cima do console e clicando em Ativar API na página de visão geral de cada API.

  5. Configure seu projeto do Android.

    1. Adicione o plug-in do Firebase Test Lab no script de build de nível superior:

      Kotlin

      plugins {
        ...
        id("com.google.firebase.testlab") version "0.0.1-alpha05" apply false
      }
      

      Groovy

      plugins {
        ...
        id 'com.google.firebase.testlab' version '0.0.1-alpha05' apply false
      }
      
    2. Ative os tipos de dispositivos personalizados no arquivo gradle.properties:

      android.experimental.testOptions.managedDevices.customDevice=true
      
    3. Adicione o plug-in do Firebase Test Lab no script de build do módulo:

      Kotlin

      plugins {
       ...
       id "com.google.firebase.testlab"
      }
      

      Groovy

      plugins {
       ...
       id 'com.google.firebase.testlab'
      }
      

Especificar um dispositivo do Test Lab

Você pode especificar um dispositivo do Firebase Test Lab para Gradle a ser usado para testar seu app no script de build do módulo. O exemplo de código a seguir cria um Pixel 3 executando o nível 30 da API como um dispositivo do Test Lab gerenciado pelo Gradle chamado ftlDevice. O bloco firebaseTestLab {} está disponível quando você aplica o plug-in com.google.firebase.testlab ao seu módulo.

Kotlin

firebaseTestLab {
  managedDevices {
    create("ftlDevice") {
      device = "Pixel3"
      apiLevel = 30
    }
  }
  ...
}

Groovy

firebaseTestLab {
  managedDevices {
    ftlDevice {
      device = "Pixel3"
      apiLevel = 30
    }
  }
  ...
}

Para definir um grupo de dispositivos gerenciados pelo Gradle, incluindo os do Firebase Test Lab, consulte Definir grupos de dispositivos.

Para executar os testes, use os mesmos comandos usados para executar outros dispositivos gerenciados pelo Gradle. Observe que o Gradle não executa testes em paralelo nem oferece suporte a outras configurações da CLI do Google Cloud para dispositivos do Test Lab.

Otimizar execuções de teste com fragmentação inteligente

Os testes em dispositivos do Test Lab gerenciados pelo Gradle oferecem suporte à fragmentação inteligente. Esse recurso distribui automaticamente seus testes entre fragmentos para que cada um deles seja executado aproximadamente ao mesmo tempo, reduzindo os esforços de alocação manual e a duração geral da execução do teste. A fragmentação inteligente também usa seu histórico de testes ou informações sobre a duração dos testes para distribuí-los da melhor maneira. A versão 0.0.1-alpha05 do plug-in do Gradle é necessária para que o Firebase Test Lab use a fragmentação inteligente.

Para ativar esse recurso, especifique o tempo necessário para os testes em cada fragmento. Defina a duração do tempo de fragmento de destino como pelo menos cinco minutos a menos que timeoutMinutes para evitar a situação em que os fragmentos são cancelados antes que os testes possam terminar.

firebaseTestLab {
  ...
  testOptions {
    targetedShardDurationMinutes = 2
  }
}

Para saber mais, leia sobre as opções de DSL de dispositivos do Firebase Test Lab.

DSL atualizada para dispositivos do Test Lab

Há mais opções de DSL que podem ser configuradas para ajudar a personalizar suas execuções de teste ou migrar de outras soluções que talvez você já esteja usando. Confira algumas dessas opções, conforme descrito no snippet de código a seguir.

firebaseTestLab {
  ...

  /**
   * A path to a JSON file that contains service account credentials to access to
   * a Firebase Test Lab project.
   */
  serviceAccountCredentials.set(file("your_service_account_credentials.json"))


  testOptions {
    fixture {
      /**
       * Whether to grant permissions on the device before tests begin.
       * Available options are "all" or "none".
       *
       * Default value is "all".
       */
      grantedPermissions = "all"

      /**
       * Map of files to push to the device before starting the test.
       *
       * The key is the location on the device.
       * The value is the location of the file, either local or in Google Cloud.
       */
      extraDeviceFiles["/sdcard/dir1/file1.txt"] = "local/file.txt"
      extraDeviceFiles["/sdcard/dir2/file2.txt"] = "gs://bucket/file.jpg"

      /**
       * The name of the network traffic profile.
       *
       * Specifies network conditions to emulate when running tests.
       *
       * Default value is empty.
       */
      networkProfile = "LTE"
    }

    execution {
      /**
       * The maximum time to run the test execution before cancellation,
       * measured in minutes. Does not include the setup or teardown of device,
       * and is handled server-side.
       *
       * The maximum possible testing time is 45 minutes on physical devices
       * and 60 minutes on virtual devices.
       *
       * Defaults to 15 minutes.
       */
       timeoutMinutes = 30

      /**
       * Number of times the test should be rerun if tests fail.
       * The number of times a test execution should be retried if one
       * or more of its test cases fail.
       *
       * The max number of times is 10.
       *
       * The default number of times is 0.
       */
      maxTestReruns = 2

      /**
       * Ensures only a single attempt is made for each execution if
       * an infrastructure issue occurs. This doesn't affect `maxTestReruns`.
       * Normally, two or more attempts are made by Firebase Test Lab if a
       * potential infrastructure issue is detected. This is best enabled for
       * latency sensitive workloads. The number of execution failures might be
       * significantly greater with `failFast` enabled.
       *
       * Defaults to false.
       */
      failFast = false

      /**
       * The number of shards to split the tests across.
       *
       * Default to 0 for no sharding.
       */
      numUniformShards = 20
    }

    /**
     * For smart sharding, the target length of time each shard should takes in
     * minutes. Maxes out at 50 shards for physical devices and 100 shards for
     * virtual devices.
     *
     * Only one of numUniformShards or targetedShardDurationMinutes can be set.
     *
     * Defaults to 0 for no smart sharding.
     */
     targetedShardDurationMinutes = 15
    }

    results {
      /**
       * The name of the Google storage bucket to store the test results in.
       *
       * If left unspecified, the default bucket is used.
       *
       * Please refer to Firebase Test Lab permissions for required permissions
       * for using the bucket.
       */
      cloudStorageBucket = "bucketLocationName"

      /**
       * Name of test results for the Firebase console history list.
       * All tests results with the same history name are grouped
       * together in the Firebase console in a time-ordered test history list.
       *
       * Defaults to the application label in the APK manifest in Flank/Fladle.
       */
      resultsHistoryName = "application-history"

      /**
       * List of paths to copy from the test device's storage to the test
       * results folder. These must be absolute paths under /sdcard or
       * /data/local/tmp.
       */
      directoriesToPull.addAll(
        "/sdcard/path/to/something"
      )

      /**
       * Whether to enable video recording during the test.
       *
       * Disabled by default.
       */
      recordVideo = false

      /**
       * Whether to enable performance metrics. If enabled, monitors and records
       * performance metrics such as CPU, memory, and network usage.
       *
       * Defaults to false.
       */
      performanceMetrics = true
  }
}