Esempio di test autostrumentati

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, come il pacchetto android , perché così facendo si porta alla situazione paradossale in cui il framework Android dovrebbe essere riavviato, che è ciò che supporta le funzioni di sistema, incluso la strumentazione stessa.

Ciò significa che un test di strumentazione non può inserirsi nel framework Android, ovvero il 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 dei sorgenti della piattaforma. Per questa categoria di test non ha senso prendere di mira un pacchetto particolare. Pertanto, è consuetudine dichiarare che tali strumentazioni abbiano come target il proprio pacchetto dell'applicazione di test, come definito nel tag <manifest> di AndroidManifest.xml .

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

  • Attività in bundle necessarie per i test.
  • Condividere 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 qui trattato riguarda la scrittura di un nuovo test di 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 farsi un'idea approssimativa prima di procedere.

Decidi la posizione della fonte

In genere il tuo team avrà già uno schema stabilito di luoghi in cui effettuare il check-in del codice e di luoghi in cui aggiungere i test. La maggior parte dei team possiede un singolo repository git o ne condivide uno con altri team ma dispone di una sottodirectory dedicata che contiene il codice sorgente del componente.

Supponendo che la posizione root dell'origine del componente sia <component source root> , la maggior parte dei componenti contiene cartelle src e tests 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 sottoposte a tests a causa della necessità di raggruppare diverse suite di test in singoli apk. E in questo caso, dovrai creare una nuova sottodirectory sotto tests .

Indipendentemente dalla struttura, finirai per popolare la directory tests o la sottodirectory appena creata con file simili a quelli contenuti nella directory instrumentation nell'esempio gerrit change. I dettagli di ciascun file sono spiegati più avanti in questo documento.

Fascicolo manifesto

Come nel caso di un progetto di app, ogni modulo di test della 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 tuo modulo di test.

Se non hai familiarità con il file AndroidManifest.xml , fai riferimento alla Panoramica del manifest dell'app

Di seguito è riportato un esempio di file 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>

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: la tua applicazione di test). Ogni utente nel sistema può installare solo un'applicazione con quel nome di pacchetto.

Inoltre, questo attributo package è lo stesso restituito da ComponentName#getPackageName() e anche lo stesso che utilizzeresti per interagire con vari comandi secondari pm utilizzando adb shell .

Tieni presente che sebbene il nome del pacchetto abbia tipicamente lo 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'altro canto, potresti optare per la semplicità e avere il nome del pacchetto Java di primo livello nella tua applicazione o test identico al nome del pacchetto dell'applicazione.

android:sharedUserId="android.uid.system"

Ciò dichiara che al momento dell'installazione, a questo file APK dovrebbe essere assegnato lo stesso ID utente, ovvero l'identità di runtime, della piattaforma principale. Tieni presente che ciò dipende dal fatto che l'apk sia firmato con lo stesso certificato della piattaforma principale (vedi LOCAL_CERTIFICATE in una 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 system del chiamante, 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" />

Ciò è necessario per tutti i test di strumentazione poiché le classi correlate sono incluse in un file di libreria JAR del framework separato, pertanto richiede voci di 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 nelle nozioni di base sui test , questa categoria di test della strumentazione è in genere destinata al test delle API del framework, quindi non è molto significativo per loro avere un pacchetto applicativo mirato specifico, diverso da se stesso.

File di configurazione semplice

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

File di configurazione complesso

Per questi casi più complessi, è necessario scrivere anche un file di configurazione di prova per il test cablaggio 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à è inclusa qui 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 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>

Ciò indica a Trade Federation di installare HelloWorldTests.apk sul dispositivo di destinazione utilizzando un target_preparer specificato. Ci sono molti preparatori di target a disposizione degli sviluppatori nella 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>

Questo specifica la classe di test della 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, consulta Configurazioni del modulo 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 basilari delle sue funzionalità. Vedere l'esempio in /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java .

Sebbene i modelli di test siano generalmente specifici per i team componenti, esistono alcuni modelli di utilizzo generalmente utili.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Una differenza significativa in JUnit4 è che non è più necessario che i test ereditino da una classe di test di base comune; invece, scrivi test in semplici classi Java e usi l'annotazione per indicare determinate impostazioni e vincoli di test. In questo esempio, stiamo indicando che questa classe dovrebbe 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 lo smontaggio post-test. Allo stesso modo, le annotazioni @BeforeClass e @AfterClass vengono utilizzate sui metodi da JUnit4 per eseguire la configurazione prima di eseguire tutti i test in una classe di test e lo smontaggio successivamente. Tieni presente che i metodi di installazione 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 che il nome del metodo inizi con test , ma ciascuno 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.

Accesso alla classe di strumentazione

Sebbene non sia trattato nell'esempio base di 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 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 Instrumentation tramite InstrumentationTestCase#getInstrumentation() , ma il nuovo test runner la 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()

Costruisci e testa localmente

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

Per casi più complessi che richiedono personalizzazioni più impegnative, seguire le istruzioni della strumentazione .