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 comsystem
, 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.