Exemplo de testes de autoinstrumentação

Quando um teste de instrumentação é iniciado, seu 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 Android precisaria ser reiniciado, que é o que suporta as funções do sistema, incluindo a própria instrumentação.

Isso significa que um teste de instrumentação não pode se injetar na estrutura do Android, também conhecido como servidor do sistema, para execução. Para testar a estrutura do Android, o código de teste pode invocar apenas superfícies de API públicas ou aquelas expostas usando a linguagem AIDL de definição de interface do Android disponível na árvore de origem da plataforma. Para esta categoria de testes, não faz sentido direcionar nenhum pacote específico. Portanto, é comum que tais instrumentações sejam declaradas para direcionar seu próprio pacote de aplicativos de teste, conforme definido em sua própria tag <manifest> de AndroidManifest.xml .

Dependendo dos requisitos, os pacotes de aplicativos de teste nesta categoria também podem:

  • Atividades do pacote necessárias para teste.
  • Compartilhe o ID do usuário com o sistema.
  • Ser assinado com a chave da plataforma.
  • Ser compilado na fonte da estrutura em vez do SDK público.

Esta categoria de testes de instrumentação é às vezes chamada de autoinstrumentação. Aqui estão alguns exemplos de testes de autoinstrumentação na plataforma fonte:

O exemplo abordado aqui é escrever um novo teste de instrumentação com pacote de destino definido em seu próprio pacote de aplicação de teste. Este guia usa o seguinte teste para servir de exemplo:

É recomendável navegar primeiro pelo código para ter uma impressão aproximada antes de prosseguir.

Decida um local de origem

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

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

Como você está adicionando um teste totalmente novo, provavelmente precisará criar o diretório tests próximo ao seu componente src e preenchê-lo com conteúdo.

Em alguns casos, sua equipe pode ter estruturas de diretório adicionais em tests devido à necessidade de empacotar diferentes conjuntos de testes em apks individuais. E neste caso, você precisará criar um novo subdiretório em tests .

Independentemente da estrutura, você acabará preenchendo o diretório tests ou o subdiretório recém-criado com arquivos semelhantes aos que estão no diretório de instrumentation na amostra de alteração do gerrit. Os detalhes de cada arquivo são explicados posteriormente neste documento.

Arquivo de manifesto

Assim como acontece com um projeto de aplicativo, cada módulo de teste de instrumentação requer um arquivo de manifesto chamado AndroidManifest.xml . Para incluir automaticamente esse arquivo usando o makefile principal BUILD_PACKAGE , forneça esse arquivo próximo ao arquivo Android.mk para seu módulo de teste.

Se você não estiver familiarizado com o arquivo AndroidManifest.xml , consulte Visão geral do manifesto do aplicativo

A seguir está 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 no 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: este é o identificador exclusivo que a estrutura do aplicativo Android usa para identificar um aplicativo (ou neste contexto: seu aplicativo de teste). Cada usuário no sistema só pode instalar um aplicativo com esse nome de pacote.

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

Observe que embora o nome do pacote normalmente tenha o mesmo estilo de um nome de pacote Java, na verdade ele tem muito pouco a ver com ele. Em outras palavras, seu pacote de aplicativo (ou teste) pode conter classes com qualquer nome de pacote, embora, por outro lado, você possa optar pela simplicidade e ter o nome do pacote Java de nível superior em seu aplicativo ou teste idêntico ao nome do pacote de aplicativo.

android:sharedUserId="android.uid.system"

Isto declara que no momento da instalação, este arquivo APK deve receber o mesmo ID de usuário, ou seja, identidade de tempo de execução, que a plataforma principal. Observe que 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 requer o mesmo certificado de assinatura
  • algumas permissões ou APIs requerem a identidade do usuário do system do chamador, o que exige que o pacote de chamada compartilhe o ID do usuário com system , se for um pacote separado da própria plataforma principal
<uses-library android:name="android.test.runner" />

Isso é necessário para todos os testes de instrumentação, uma vez que as classes relacionadas são empacotadas em um arquivo de biblioteca JAR de estrutura separado e, portanto, requer entradas de caminho de classe adicionais quando o pacote de teste é chamado pela estrutura do aplicativo.

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

Você deve ter notado que targetPackage aqui é declarado igual ao atributo package declarado na tag de manifest deste arquivo. Conforme mencionado em Noções básicas de teste , esta categoria de teste de instrumentação normalmente se destina a testar APIs de estrutura, portanto, não faz muito sentido que eles tenham um pacote de aplicativos direcionado específico, além dele próprio.

Arquivo de configuração simples

Cada novo módulo de teste deve ter um arquivo de configuração para direcionar o sistema de construção 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 Blueprint baseada em Soong é suficiente. Para obter detalhes, consulte Configuração de teste simples .

Arquivo de configuração complexo

Para esses casos mais complexos, você também precisa escrever um arquivo de configuração de teste para o equipamento de teste do Android, Trade Federation .

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

Um instantâneo está incluído aqui por conveniência:

<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 sobre o arquivo de configuração de teste:

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

Isso diz à Trade Federation para instalar o HelloWorldTests.apk no dispositivo de destino usando um target_preparer especificado. Existem muitos preparadores de alvo 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 Trade Federation a ser usada para executar o teste e passa no pacote no dispositivo a ser executado e a estrutura do executor de teste que é JUnit neste caso.

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

Recursos JUnit4

Usar a biblioteca android-support-test como executor de teste permite a adoção de novas classes de teste no estilo JUnit4, e a amostra de alteração do gerrit contém alguns usos muito básicos de seus recursos. Veja o exemplo em /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java .

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

@RunWith(JUnit4.class)
public class HelloWorldTest {

Uma diferença significativa no JUnit4 é que os testes não precisam mais herdar de uma classe de teste base comum; em vez disso, você escreve testes em classes Java simples e usa anotações para indicar determinadas configurações e restrições de teste. Neste exemplo, estamos instruindo que esta classe deve ser executada como um teste 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 do 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 do JUnit4 para realizar a configuração antes de executar todos os testes em uma classe de teste e desmontá-la posteriormente. Observe que os métodos de configuração e desmontagem do escopo de classe devem ser estáticos. Quanto aos métodos de teste, diferentemente 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 deve ser anotado com @Test . Como sempre, os métodos de teste devem ser públicos, não declarar valor de retorno, não aceitar parâmetros e podem gerar exceções.

Acesso à classe de instrumentação

Embora não seja abordado no exemplo básico do hello world, é bastante comum que um teste Android exija acesso à instância Instrumentation : esta é a interface principal da API que fornece acesso a contextos de aplicativos, APIs de teste relacionadas ao ciclo de vida da atividade e muito mais.

Como os testes JUnit4 não exigem mais uma classe base comum, não é mais necessário obter a instância Instrumentation por meio de InstrumentationTestCase#getInstrumentation() ; em vez disso, o novo executor de teste a gerencia por meio de InstrumentationRegistry , onde a configuração contextual e ambiental criada pela estrutura 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()

Crie e teste localmente

Para os casos de uso mais comuns, utilize Atest .

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