Exemplo de testes de autoinstrumentação

Quando um teste de instrumentação é iniciado, o pacote de destino é reiniciado com o código de instrumentação injetado e iniciado para execução. Uma exceção é que o pacote de destino aqui não pode ser o próprio framework do aplicativo Android, como o pacote android, porque isso leva à situação paradoxal em que o framework do Android precisa ser reiniciado, o que é o que oferece suporte às funções do sistema, incluindo a própria instrumentação.

Isso significa que um teste de instrumentação não pode ser injetado no framework do Android, também conhecido como servidor do sistema, para execução. Para testar o framework do Android, o código de teste pode invocar apenas superfícies de API públicas ou aquelas expostas usando a Linguagem de definição de interface do Android AIDL disponível na árvore de origem da plataforma. Para essa categoria de testes, não é significativo segmentar nenhum pacote específico. Portanto, é comum que essas instrumentações sejam declaradas para segmentar o próprio pacote de aplicativo de teste, conforme definido na própria tag <manifest> de AndroidManifest.xml.

Dependendo dos requisitos, os pacotes de teste de aplicativos nessa categoria também podem fazer o seguinte:

  • Agrupar atividades necessárias para testes.
  • Compartilhe o ID do usuário com o sistema.
  • Ser assinado com a chave da plataforma.
  • Ser compilado com base na origem do framework, e não no SDK público.

Essa categoria de testes de instrumentação às vezes é chamada de autoinstrumentação. Confira alguns exemplos de testes de autoinstrumentação na fonte da plataforma:

O exemplo abordado aqui é escrever um novo teste de instrumentação com o pacote alvo definido no próprio pacote do aplicativo de teste. Este guia usa o seguinte teste como exemplo:

Recomendamos navegar pelo código primeiro para ter uma impressão antes de continuar.

Decidir um local de origem

Normalmente, sua equipe já tem um padrão estabelecido de locais para fazer check-in do código e locais para adicionar testes. A maioria das equipes tem um único repositório Git ou compartilha um com outras equipes, mas tem um subdiretório dedicado que contém o código-fonte do componente.

Supondo que o local raiz da origem do componente esteja em <component source root>, a maioria dos componentes tem pastas src e tests abaixo dela e alguns arquivos adicionais, como Android.mk (ou divididos em outros arquivos .mk), o arquivo de manifesto AndroidManifest.xml e o arquivo de configuração de teste 'AndroidTest.xml'.

Como você está adicionando um teste novo, provavelmente será necessário criar o diretório tests ao lado do componente src e preenchê-lo com conteúdo.

Em alguns casos, sua equipe pode ter outras estruturas de diretório em tests devido à necessidade de empacotar diferentes conjuntos de testes em APKs individuais. Nesse caso, você vai precisar criar um novo subdiretório em tests.

Independentemente da estrutura, você vai preencher o diretório tests ou o subdiretório recém-criado com arquivos semelhantes aos que estão no diretório instrumentation na mudança de exemplo do gerrit. Os detalhes de cada arquivo são explicados mais adiante neste documento.

Arquivo de manifesto

Assim como em um projeto de app, cada módulo de teste de instrumentação requer um arquivo de manifesto chamado AndroidManifest.xml. Para incluir automaticamente esse arquivo usando o makefile do núcleo BUILD_PACKAGE, forneça esse arquivo ao lado do arquivo Android.mk do módulo de teste.

Se você não conhece o arquivo AndroidManifest.xml, consulte a Visão geral do manifesto do app.

Confira a seguir um exemplo de arquivo AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  android:sharedUserId="android.uid.system"
  package="android.test.example.helloworld" >

    <application>
       <uses-library android:name="android.test.runner"/>
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.test.example.helloworld"
                     android:label="Hello World Test"/>

</manifest>

Algumas observações selecionadas sobre o arquivo de manifesto:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.test.example.helloworld" >

O atributo package é o nome do pacote do aplicativo: é o identificador exclusivo que o framework do aplicativo Android usa para identificar um aplicativo (ou, neste contexto, o aplicativo de teste). Cada usuário no sistema só pode instalar um aplicativo com esse nome de pacote.

Além disso, esse atributo package é o mesmo que ComponentName#getPackageName() retorna e também o mesmo que você usaria para interagir com vários subatributos pm usando adb shell.

Embora o nome do pacote normalmente tenha o mesmo estilo que o nome de um pacote Java, ele tem poucas coisas em comum com ele. Em outras palavras, seu pacote de aplicativo (ou teste) pode conter classes com qualquer nome de pacote. Por outro lado, você pode optar pela simplicidade e ter o nome do pacote Java de nível superior no aplicativo ou um teste idêntico ao nome do pacote do aplicativo.

android:sharedUserId="android.uid.system"

Isso declara que, no momento da instalação, esse arquivo APK precisa receber o mesmo ID do usuário, ou seja, a identidade de execução, como a plataforma principal. Isso depende do APK ser assinado com o mesmo certificado da plataforma principal (consulte LOCAL_CERTIFICATE em uma seção anterior), mas são conceitos diferentes:

  • Algumas permissões ou APIs são protegidas por assinatura, o que exige o mesmo certificado de assinatura
  • algumas permissões ou APIs exigem a identidade do usuário system do autor da chamada, o que exige que o pacote de chamada compartilhe o ID do usuário com system, se for um pacote separado da plataforma principal
<uses-library android:name="android.test.runner" />

Isso é necessário para todos os testes de instrumentação, já que as classes relacionadas são empacotadas em um arquivo de biblioteca JAR de framework separado. Portanto, são necessárias entradas de classpath adicionais quando o pacote de teste é invocado pelo framework do aplicativo.

android:targetPackage="android.test.example.helloworld"

Talvez você tenha notado que o targetPackage aqui é declarado da mesma forma que o atributo package declarado na tag manifest deste arquivo. Como mencionado nos conceitos básicos de testes, essa categoria de teste de instrumentação é normalmente destinada a APIs de framework de teste. Portanto, não é muito significativo que elas tenham um pacote de aplicativo específico, exceto o próprio pacote.

Arquivo de configuração simples

Cada novo módulo de teste precisa ter um arquivo de configuração para direcionar o sistema de build com metadados do módulo, dependências de tempo de compilação e instruções de empacotamento. Na maioria dos casos, a opção de arquivo de blueprint baseada em Soong é suficiente. Para mais detalhes, consulte Configuração simples de teste.

Arquivo de configuração complexo

Para esses casos mais complexos, também é necessário escrever um arquivo de configuração de teste para o harness de teste do Android, Trade Federation.

A configuração de teste pode especificar opções especiais de configuração do dispositivo e argumentos padrão para fornecer a classe de teste. Consulte o exemplo em /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Por conveniência, incluímos um snapshot aqui:

<configuration description="Runs sample instrumentation test.">
  <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
  <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
    <option name="test-file-name" value="HelloWorldTests.apk"/>
  </target_preparer>
  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
  <option name="test-suite-tag" value="apct"/>
  <option name="test-tag" value="SampleInstrumentationTest"/>

  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
    <option name="package" value="android.test.example.helloworld"/>
    <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
  </test>
</configuration>

Algumas observações selecionadas no arquivo de configuração do teste:

<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
  <option name="test-file-name" value="HelloWorldTests.apk"/>
</target_preparer>

Isso informa à Trade Federation para instalar o HelloWorldTests.apk no dispositivo de destino usando um target_preparer especificado. Há muitos preparadores de destino disponíveis para desenvolvedores na Trade Federation e eles podem ser usados para garantir que o dispositivo seja configurado corretamente antes da execução do teste.

<test class="com.android.tradefed.testtype.AndroidJUnitTest">
  <option name="package" value="android.test.example.helloworld"/>
  <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
</test>

Isso especifica a classe de teste da Trade Federation a ser usada para executar o teste e transmite o pacote no dispositivo a ser executado e o framework do executor de testes, que é o JUnit neste caso.

Para mais informações, consulte Configurações de teste do módulo.

Recursos do JUnit4

O uso da biblioteca android-support-test como executor de testes permite a adoção de novas classes de teste no estilo JUnit4. O exemplo de mudança no gerrit contém alguns usos muito básicos dos recursos. Consulte o exemplo em /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Embora os padrões de teste geralmente sejam específicos para equipes de componentes, há alguns padrões de uso úteis.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Uma diferença significativa no JUnit4 é que os testes não precisam mais ser herdados de uma classe de teste base comum. Em vez disso, você escreve os testes em classes Java simples e usa anotações para indicar determinadas configurações e restrições de teste. Neste exemplo, estamos instruindo que essa classe precisa ser executada como um teste do JUnit4.

    @BeforeClass
    public static void beforeClass() {
    ...
    @AfterClass
    public static void afterClass() {
    ...
    @Before
    public void before() {
    ...
    @After
    public void after() {
    ...
    @Test
    @SmallTest
    public void testHelloWorld() {
    ...

As anotações @Before e @After são usadas em métodos pelo JUnit4 para realizar a configuração pré-teste e a desmontagem pós-teste. Da mesma forma, as anotações @BeforeClass e @AfterClass são usadas em métodos pelo JUnit4 para realizar a configuração antes de executar todos os testes em uma classe de teste e desmontar depois. Os métodos de configuração e desmontagem do escopo da classe precisam ser estáticos. Quanto aos métodos de teste, ao contrário da versão anterior do JUnit, eles não precisam mais iniciar o nome do método com test. Em vez disso, cada um deles precisa ser anotado com @Test. Como de costume, os métodos de teste precisam ser públicos, declarar nenhum valor de retorno, não usar parâmetros e poder gerar exceções.

Acesso à classe de instrumentação

Embora não tenha sido abordado no exemplo básico de Hello World, é bastante comum que um teste do Android exija acesso à instância Instrumentation: essa é a interface de API principal que fornece acesso a contextos de aplicativos, APIs de teste relacionadas ao ciclo de vida de atividades e muito mais.

Como os testes do JUnit4 não precisam mais de uma classe base comum, não é mais necessário acessar a instância Instrumentation usando InstrumentationTestCase#getInstrumentation(). Em vez disso, o novo executor de teste a gerencia usando InstrumentationRegistry, onde a configuração contextual e ambiental criada pelo framework de instrumentação é armazenada.

Para acessar a instância da classe Instrumentation, basta chamar o método estático getInstrumentation() na classe InstrumentationRegistry:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Criar e testar localmente

Para os casos de uso mais comuns, use o Atest.

Para casos mais complexos que exigem uma personalização mais pesada, siga as instruções de instrumentação.