Esempio di test autostrumentali

Quando viene avviato un test di strumentazione, il relativo pacchetto di destinazione viene riavviato con il codice di strumentazione iniettato e avviato per l'esecuzione. Un'eccezione è che il pacchetto di destinazione qui non può essere il framework dell'applicazione Android stesso, ovvero il pacchetto android , perché così facendo porterebbe alla situazione paradossale in cui sarebbe necessario riavviare il framework Android, che è ciò che supporta le funzioni di sistema, inclusa la strumentazione si.

Ciò significa che un test della strumentazione non può iniettarsi nel framework Android, noto anche come server di sistema, per l'esecuzione. Per testare il framework Android, il codice di test può richiamare solo superfici API pubbliche o quelle esposte tramite Android Interface Definition Language AIDL disponibile nell'albero dei sorgenti della piattaforma. Per questa categoria di test, non è significativo scegliere come target un pacchetto particolare. Pertanto, è consuetudine che tali strumentazioni siano dichiarate per indirizzare 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 possono anche:

  • Raggruppa le attività 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 strumentali viene talvolta definita autostrumentazione. Ecco alcuni esempi di test di autostrumentazione nel sorgente della piattaforma:

L'esempio trattato qui è la scrittura di un nuovo test della strumentazione con il pacchetto di destinazione impostato nel proprio pacchetto dell'applicazione di test. Questa guida utilizza il seguente test come esempio:

Si consiglia di sfogliare prima il codice per avere un'idea approssimativa prima di procedere.

Decidere una posizione di origine

In genere il tuo team avrà già uno schema stabilito di luoghi in cui archiviare il codice e luoghi in cui aggiungere 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 per l'origine del componente sia in <component source root> , la maggior parte dei componenti ha cartelle src e tests sotto di essa e alcuni file aggiuntivi come Android.mk (o suddivisi in file .mk aggiuntivi), il file manifest AndroidManifest.xml e il file di configurazione del test "AndroidTest.xml".

Dato che stai aggiungendo un test nuovo di zecca, probabilmente dovrai creare la directory tests accanto al tuo componente src e popolarla con il contenuto.

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

Indipendentemente dalla struttura, finirai per popolare la directory dei tests o la sottodirectory appena creata con file simili a quelli che si trovano nella directory della instrumentation nel cambio di gerrit del campione. Le sezioni seguenti spiegheranno in ulteriori dettagli di ciascun file.

File manifesto

Proprio come una normale applicazione, ogni modulo di test della strumentazione necessita di un file manifest. Se assegni al file il nome AndroidManifest.xml e lo fornisci accanto ad Android.mk per il modulo di test, verrà incluso automaticamente dal makefile principale di BUILD_PACKAGE .

Prima di procedere ulteriormente, si consiglia vivamente di esaminare prima la Panoramica del manifesto dell'app .

Ciò fornisce una panoramica dei componenti di base di un file manifest e delle relative funzionalità. Vedere l'esempio in platform_testing/tests/example/instrumentation/AndroidManifest.xml .

Un'istantanea è inclusa qui per comodità:

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

    <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: questo è l'identificatore univoco che il framework dell'applicazione Android utilizza per identificare un'applicazione (o in questo contesto: l'applicazione di test). Ogni utente nel sistema può installare solo un'applicazione con quel nome di pacchetto.

Inoltre, questo attributo del package è lo stesso restituito da ComponentName#getPackageName() e anche lo stesso che useresti per interagire con vari comandi pm sub tramite adb shell .

Si noti inoltre che sebbene il nome del pacchetto sia in genere nello stesso stile del nome di un pacchetto Java, in realtà ha pochissime cose a che fare con esso. In altre parole, il pacchetto della tua applicazione (o 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 livello superiore nella tua applicazione o test identico al nome del pacchetto dell'applicazione.

android:sharedUserId="android.uid.system"

Questo dichiara che al momento dell'installazione, a questo apk dovrebbe essere concesso lo stesso ID utente, cioè l'identità di runtime, della piattaforma principale. Nota che ciò dipende dalla firma dell'apk con lo stesso certificato della piattaforma principale (vedi LOCAL_CERTIFICATE nella sezione precedente), ma sono 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 di system del chiamante, che richiede che il pacchetto chiamante condivida l'ID utente con il system , se è un pacchetto separato dalla piattaforma principale stessa
<uses-library android:name="android.test.runner" />

Ciò è richiesto per tutti i test di strumentazione poiché le classi correlate sono impacchettate in un file di libreria jar del framework separato, pertanto richiede voci del percorso di classe 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 menzionato in test di base , questa categoria di test di strumentazione è in genere destinata al test delle API del framework, quindi non è molto significativo per loro avere un pacchetto di applicazioni mirato 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 i metadati del modulo, le dipendenze in fase di compilazione e le istruzioni di confezionamento. Nella maggior parte dei casi, l'opzione del file Blueprint basata su Soong è sufficiente. Per i dettagli, vedere Configurazione test semplice .

File di configurazione complesso

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

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

Un'istantanea è inclusa qui per comodità:

<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 note selezionate sul file di configurazione del test:

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

Questo dice a Trade Federation di installare HelloWorldTests.apk sul dispositivo di destinazione utilizzando un target_preparer specificato. Ci sono molti preparatori di destinazione disponibili per gli sviluppatori in Trade Federation e questi 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 nel pacchetto sul dispositivo da eseguire e il framework del test runner che in questo caso è JUnit.

Per ulteriori informazioni, vedere Configurazioni del modulo di test .

Caratteristiche 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 gerrit di esempio contiene un uso molto semplice delle sue funzionalità. Vedi l'esempio in /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java .

Sebbene i modelli di test siano in genere specifici per i team dei componenti, esistono alcuni modelli 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 test in classi Java semplici e usi l'annotazione per indicare determinate impostazioni e vincoli di 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 di JUnit4 per eseguire la configurazione pre-test e lo smontaggio post-test. Allo stesso modo, le annotazioni @BeforeClass e @AfterClass vengono utilizzate sui metodi di JUnit4 per eseguire l'installazione prima di eseguire tutti i test in una classe di test e successivamente lo smontaggio. Si noti che i metodi di configurazione e smontaggio dell'ambito della classe devono essere statici. Per quanto riguarda i metodi di test, a differenza della versione precedente di JUnit, non è più necessario iniziare il nome del metodo con test , ma ognuno di essi deve essere annotato con @Test . Come al solito, i metodi di test devono essere pubblici, non dichiarare alcun valore restituito, non accettare parametri e possono generare eccezioni.

Importante : i metodi di test stessi sono annotati con l'annotazione @Test ; e si noti che affinché i test vengano eseguiti tramite APCT, devono essere annotati con le dimensioni dei test: l'esempio ha annotato il metodo testHelloWorld come @SmallTest . L'annotazione può essere applicata nell'ambito del metodo o nell'ambito della classe.

Accesso alla instrumentation

Sebbene non sia trattato nell'esempio di base di Hello World, è abbastanza comune che un test Android richieda l'accesso all'istanza di Instrumentation : questa è l'interfaccia API principale che fornisce l'accesso ai contesti dell'applicazione, alle API di test relative 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 di Instrumentation tramite InstrumentationTestCase#getInstrumentation() , ma il nuovo test runner lo gestisce tramite InstrumentationRegistry dove viene archiviata 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 e testa localmente

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

Per i casi più complessi che richiedono una personalizzazione più pesante, seguire le istruzioni della strumentazione .