Esempio di test di autostrumentazione

Quando viene avviato un test di strumentazione, il relativo pacchetto di destinazione viene riavviato con il codice di strumentazione inserito e avviato per l'esecuzione. Un'eccezione è che il pacchetto di destinazione qui non può essere il framework dell'applicazione Android stesso, ad esempio il pacchetto android, perché ciò porta alla situazione paradossale in cui il framework Android dovrebbe essere riavviato, il che supporta le funzioni di sistema, inclusa l'instrumentation stessa.

Ciò significa che un test di instrumentazione non può essere inserito nel framework Android, ovvero nel server di sistema, per l'esecuzione. Per testare il framework Android, il codice di test può richiamare solo interfacce API pubbliche o quelle esposte utilizzando Android Interface Definition Language AIDL disponibili nell'albero dei sorgenti della piattaforma. Per questa categoria di test, non è significativo scegliere come target un pacchetto specifico. Pertanto, è consuetudine che queste strumentazioni vengano dichiarate per avere come target il proprio pacchetto di applicazioni di test, come definito nel proprio tag <manifest> di AndroidManifest.xml.

A seconda dei requisiti, i pacchetti di applicazioni di test in questa categoria potrebbero anche:

  • Attività del bundle necessarie per il test.
  • Condividi l'ID utente con il sistema.
  • Essere firmato con la chiave della piattaforma.
  • Essere compilato in base all'origine del framework anziché all'SDK pubblico.

Questa categoria di test di strumentazione è talvolta chiamata strumentazione automatica. Ecco alcuni esempi di test di autostrumentazione nel codice sorgente della piattaforma:

L'esempio trattato qui riguarda la scrittura di un nuovo test di strumentazione con il pacchetto target impostato sul proprio pacchetto dell'applicazione di test. Questa guida utilizza il seguente test come esempio:

Ti consigliamo di sfogliare prima il codice per farti un'idea generale prima di procedere.

Scegliere una posizione di origine

In genere, il tuo team avrà già un pattern consolidato di luoghi in cui controllare il codice e luoghi in cui aggiungere i test. La maggior parte dei team possiede un singolo repository Git oppure ne condivide uno con altri team, ma ha una sottodirectory dedicata che contiene il codice sorgente del componente.

Supponendo che la posizione principale dell'origine del componente sia <component source root>, la maggior parte dei componenti ha le cartelle src e tests al suo interno, oltre ad alcuni file aggiuntivi come Android.mk (o suddivisi in altri file .mk), il file manifest AndroidManifest.xml e il file di configurazione del test "AndroidTest.xml".

Poiché stai aggiungendo un test completamente nuovo, probabilmente dovrai creare la directory tests accanto al componente src e popolarla con i contenuti.

In alcuni casi, il tuo team potrebbe avere ulteriori strutture di directory in tests a causa della necessità di raggruppare diverse suite di test in APK individuali. In questo caso, dovrai creare una nuova sottodirectory in tests.

Indipendentemente dalla struttura, alla fine la directory tests o la sottodirectory appena creata verrà compilata con file simili a quelli della directory instrumentation nella modifica di Gerrit di esempio. I dettagli di ogni file vengono spiegati più avanti in questo documento.

File manifest

Come per un progetto di app, ogni modulo di test di strumentazione richiede un file manifest denominato AndroidManifest.xml. Per includere automaticamente questo file utilizzando il makefile principaleBUILD_PACKAGE, fornisci questo file accanto al file Android.mk per il modulo di test.

Se non hai familiarità con il file AndroidManifest.xml, consulta la panoramica del manifest dell'app.

Di seguito è riportato un file AndroidManifest.xml di esempio:

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

Alcuni commenti selezionati sul file manifest:

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

L'attributo package è il nome del pacchetto dell'applicazione: si tratta dell'identificatore univoco che il framework dell'applicazione Android utilizza per identificare un'applicazione (o, in questo contesto, la tua applicazione di test). Ogni utente del sistema può installare una sola applicazione con quel nome del pacchetto.

Inoltre, questo attributo package è uguale a quello restituito da ComponentName#getPackageName() ed è lo stesso che utilizzeresti per interagire con vari sottocomandi pm utilizza adb shell.

Tieni presente che, sebbene il nome del pacchetto abbia in genere lo stesso stile di un nome di pacchetto Java, in realtà ha ben poco a che fare con quest'ultimo. In altre parole, il pacchetto dell'applicazione (o del test) può contenere classi con qualsiasi nome del pacchetto, anche se, d'altra parte, potresti optare per la semplicità e avere il nome del pacchetto Java di primo livello nell'applicazione o nel test identico al nome del pacchetto dell'applicazione.

android:sharedUserId="android.uid.system"

Ciò dichiara che al momento dell'installazione, a questo file APK deve essere concesso lo stesso ID utente, ovvero la stessa identità di runtime, della piattaforma principale. Tieni presente che questo dipende dal fatto che l'APK sia firmato con lo stesso certificato della piattaforma principale (vedi LOCAL_CERTIFICATE in una sezione precedente), ma si tratta di concetti diversi:

  • alcune autorizzazioni o API sono protette dalla firma, il che richiede lo stesso certificato di firma
  • alcune autorizzazioni o API richiedono l'identità utente system del chiamante, che richiede al pacchetto chiamante di condividere l'ID utente con system, se si tratta di un pacchetto separato dalla piattaforma principale
<uses-library android:name="android.test.runner" />

Questo è necessario per tutti i test di strumentazione, poiché le classi correlate sono inserite in un file JAR della libreria del framework separato, pertanto richiedono voci classpath aggiuntive quando il pacchetto di test viene richiamato dal framework dell'applicazione.

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

Potresti aver notato che targetPackage qui è dichiarato come l'attributo package dichiarato nel tag manifest di questo file. Come indicato nelle nozioni di base sui test, questa categoria di test di strumentazione è in genere destinata alle API del framework di test, quindi non è molto significativo che abbiano un pacchetto di applicazioni di destinazione specifico, diverso da se stesso.

File di configurazione semplice

Ogni nuovo modulo di test deve avere un file di configurazione per indirizzare il sistema di compilazione con metadati del modulo, dipendenze in fase di compilazione e istruzioni di packaging. Nella maggior parte dei casi, l'opzione di file Blueprint basata su Soong è sufficiente. Per maggiori dettagli, vedi Configurazione semplice del test.

File di configurazione complesso

Per questi casi più complessi, devi anche scrivere un file di configurazione di test per lo strumento di test di Android, Trade Federation.

La configurazione del test può specificare opzioni speciali di configurazione del dispositivo e argomenti predefiniti per fornire la classe di test. Vedi l'esempio in /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Per comodità, è incluso uno snapshot:

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

Alcuni commenti selezionati sul file di configurazione del test:

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

Indica a Trade Federation di installare HelloWorldTests.apk sul dispositivo di destinazione utilizzando un target_preparer specificato. In Trade Federation sono disponibili molti preparatori di target e possono essere utilizzati per garantire che il dispositivo sia configurato correttamente prima dell'esecuzione del test.

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

Specifica la classe di test Trade Federation da utilizzare per eseguire il test e passa il pacchetto sul dispositivo da eseguire e il framework di esecuzione dei test che in questo caso è JUnit.

Per saperne di più, consulta Configurazioni dei moduli di test.

Funzionalità di JUnit4

L'utilizzo della libreria android-support-test come test runner consente l'adozione di nuove classi di test in stile JUnit4 e la modifica di esempio di Gerrit contiene alcuni utilizzi molto semplici delle sue funzionalità. Vedi l'esempio in /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Sebbene i pattern di test siano in genere specifici per i team dei componenti, esistono alcuni pattern di utilizzo generalmente utili.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Una differenza significativa in JUnit4 è che i test non devono più ereditare da una classe di test di base comune; al contrario, scrivi i test in classi Java semplici e utilizza le annotazioni per indicare determinate configurazioni e vincoli dei test. In questo esempio, stiamo indicando che questa classe deve essere eseguita come test JUnit4.

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

Le annotazioni @Before e @After vengono utilizzate nei metodi di JUnit4 per eseguire la configurazione pre-test e l'eliminazione post-test. Analogamente, le annotazioni @BeforeClass e @AfterClass vengono utilizzate nei metodi da JUnit4 per eseguire la configurazione prima di eseguire tutti i test in una classe di test e la pulizia successiva. Tieni presente che i metodi di configurazione e pulizia dell'ambito della classe devono essere statici. Per quanto riguarda i metodi di test, a differenza delle versioni precedenti di JUnit, non è più necessario iniziare il nome del metodo con test. Ogni metodo deve invece essere annotato con @Test. Come di consueto, i metodi di test devono essere pubblici, non dichiarare alcun valore restituito, non accettare parametri e possono generare eccezioni.

Accesso alla classe di strumentazione

Sebbene non sia trattato nell'esempio di base Hello World, è abbastanza comune che un test Android richieda l'accesso all'istanza Instrumentation: questa è l'interfaccia API principale che fornisce l'accesso ai contesti delle applicazioni, alle API di test correlate al ciclo di vita delle attività e altro ancora.

Poiché i test JUnit4 non richiedono più una classe base comune, non è più necessario ottenere l'istanza Instrumentation tramite InstrumentationTestCase#getInstrumentation(). Il nuovo test runner la gestisce tramite InstrumentationRegistry, dove vengono archiviate la configurazione contestuale e ambientale creata dal framework di strumentazione.

Per accedere all'istanza della classe Instrumentation, chiama semplicemente il metodo statico getInstrumentation() sulla classe InstrumentationRegistry:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Creare build e testare in locale

Per i casi d'uso più comuni, utilizza Atest.

Per i casi più complessi che richiedono una personalizzazione più approfondita, segui le istruzioni di strumentazione.