Esempio di test di autostrumentazione

Quando viene avviato un test di strumentazione, il 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'app per Android stesso, ad esempio il pacchetto android, perché in questo modo si verifica una situazione paradossale in cui il framework Android dovrebbe essere riavviato, che è ciò che supporta le funzioni di sistema, inclusa la strumentazione stessa.

Ciò significa che un test di strumentazione 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 le superfici API pubbliche o quelle esposte utilizzando Android Interface Definition Language AIDL disponibile nell'albero di origine della piattaforma. Per questa categoria di test, non è significativo scegliere come target un pacchetto specifico. Pertanto, è consuetudine che queste strumentazioni vengano dichiarate come target del proprio pacchetto di applicazioni di test, come definito nel tag <manifest> di AndroidManifest.xml.

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

  • Bundle di attività necessarie per i test.
  • Condividere l'ID utente con il sistema.
  • Essere firmati con la chiave della piattaforma.
  • Essere compilati in base all'origine del framework anziché all'SDK pubblico.

Questa categoria di test di strumentazione viene talvolta definita autostrumentazione. Ecco alcuni esempi di test di autostrumentazione nell'origine della piattaforma:

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

Prima di procedere, ti consigliamo di sfogliare il codice per farti un'idea generale.

Scegliere una posizione di origine

In genere, il tuo team avrà già un pattern consolidato di posizioni da controllare nel codice e posizioni in cui aggiungere i test. La maggior parte dei team possiede un singolo repository Git o 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 file .mk aggiuntivi), il file manifest AndroidManifest.xml, e il file di configurazione dei test 'AndroidTest.xml'.

Poiché stai aggiungendo un nuovo test, probabilmente dovrai creare la directory tests accanto a src del componente e inserirvi i contenuti.

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

Indipendentemente dalla struttura, finirai per inserire nella directory tests o nella sottodirectory appena creata file simili a quelli della directory instrumentation nella modifica di Gerrit di esempio. I dettagli di ogni file sono 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 principale BUILD_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 file 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>

Alcune osservazioni 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'app: si tratta dell'identificatore univoco che il framework dell'app per 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 è lo stesso restituito da ComponentName#getPackageName() e lo stesso che utilizzeresti per interagire con vari sottocomandi pm utilizzando adb shell.

Tieni presente che, sebbene il nome del pacchetto sia in genere nello stesso stile di un nome di pacchetto Java, in realtà ha pochissimo a che fare con esso. In altre parole, il pacchetto dell'applicazione (o di test) può contenere classi con qualsiasi nome di 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"

Questo dichiara che al momento dell'installazione a questo file APK deve essere concesso lo stesso ID utente, ovvero l'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, il che richiede che il pacchetto chiamante condivida l'ID utente con system, se si tratta di un pacchetto separato dalla piattaforma principale stessa.
<uses-library android:name="android.test.runner" />

Questo è necessario per tutti i test di strumentazione, poiché le classi correlate sono incluse in un file JAR della libreria del framework separato, pertanto richiede 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 uguale all'attributo package dichiarato nel tag manifest di questo file. Come accennato in nozioni di base sui test, questa categoria di test di strumentazione è in genere destinata a testare le API del framework, quindi non è molto significativo che abbiano un pacchetto di applicazioni di destinazione specifico, a parte se stesso.

File di configurazione semplice

Ogni nuovo modulo di test deve avere un file di configurazione per indicare al sistema di compilazione i metadati del modulo, le dipendenze in fase di compilazione e le istruzioni di pacchettizzazione. Nella maggior parte dei casi, l'opzione del file Blueprint basato su Soong è sufficiente. Per maggiori dettagli, vedi Configurazione semplice dei test.

File di configurazione complesso

Per questi casi più complessi, devi anche scrivere un file di configurazione dei test per l'ambiente di test di Android, Trade Federation.

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

Per comodità, qui è inclusa un'istantanea:

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

Alcune osservazioni sul file di configurazione dei 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 destinazione per gli sviluppatori, che possono essere utilizzati per assicurarsi che il dispositivo sia configurato correttamente prima dell'esecuzione dei 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 di Trade Federation da utilizzare per eseguire il test e passa il pacchetto sul dispositivo da eseguire e il framework del runner di test, che in questo caso è JUnit.

Per maggiori informazioni, vedi Configurazione del modulo di test.

Funzionalità JUnit4

L'utilizzo della libreria android-support-test come runner di test consente l'adozione di nuove classi di test in stile JUnit4 e la modifica di Gerrit di esempio contiene alcuni utilizzi di base 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 di 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; invece, scrivi i test in classi Java semplici e utilizzi 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 sui metodi da JUnit4 per eseguire la configurazione pre-test e la pulizia post-test. Allo stesso modo, le annotazioni @BeforeClass e @AfterClass vengono utilizzate sui metodi da JUnit4 per eseguire la configurazione prima dell'esecuzione di tutti i test in una classe di test e la pulizia successivamente. 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, ma ognuno di essi deve 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 di Hello World, è abbastanza comune che un test Android richieda l'accesso all'istanza Instrumentation: si tratta dell'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 di base comune, non è più necessario ottenere l'istanza Instrumentation tramite InstrumentationTestCase#getInstrumentation(), ma il nuovo runner di test la gestisce tramite InstrumentationRegistry in cui sono memorizzate 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()

Crea build ed esegui test 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.