Esempio di test di auto-strumentazione

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 di applicazione Android stesso, ad esempio il pacchetto android, perché ciò porta alla situazione paradossale in cui il framework Android dovrebbe essere riavviato, che supporta le funzioni di sistema, inclusa la stessa strumentazione.

Ciò significa che un test di 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 interfacce API pubbliche o quelle esposte utilizzando Android Interface Definition Language (AIDL) disponibile nella struttura della sorgente della piattaforma. Per questa categoria di test, non è significativo come target un pacchetto specifico. Pertanto, è consuetudine dichiarare che queste misurazioni hanno 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 possono anche:

  • Raggruppa le attività necessarie per i test.
  • Condividi l'ID utente con il sistema.
  • Essere firmate con la chiave della piattaforma.
  • Essere compilato in base all'origine del framework anziché all'SDK pubblico.

Questa categoria di test di misurazione è talvolta indicata come automisurazione. Ecco alcuni esempi di test di autostrumentazione nel codice sorgente della piattaforma:

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

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

Scegli una posizione di origine

In genere, il tuo team avrà già stabilito un modello di punti di controllo nel codice e punti in cui aggiungere test. La maggior parte dei team possiede un singolo repository Git o lo condivide con altri team, ma dispone di una sottodirectory dedicata che contiene il codice sorgente dei componenti.

Supponendo che la posizione principale del codice sorgente del componente sia <component source root>, la maggior parte dei componenti ha sotto le 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'.

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

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

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

File manifest

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

Se non hai dimestichezza 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 note selezionate 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 utilizzato dal framework per le applicazioni Android 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 anche quello che utilizzeresti per interagire con i vari comandi secondari pm utilizzando adb shell.

Tieni presente che, anche se il nome del pacchetto è in genere nello stesso stile di un nome di pacchetto Java, in realtà non ha molto a che fare con esso. In altre parole, il pacchetto dell'applicazione (o del test) può contenere classi con qualsiasi nome del pacchetto, anche se, d'altra parte, puoi 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 identità di runtime, della piattaforma di base. Tieni presente che questo dipende dalla firma dell'APK con lo stesso certificato della piattaforma di base (vedi LOCAL_CERTIFICATE in una sezione precedente), ma si tratta di concetti diversi:

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

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

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

Potresti aver notato che targetPackage qui viene dichiarato uguale all'attributo package nel tag manifest di questo file. Come accennato nelle nozioni di base sui test, questa categoria di test di strumentazione è in genere destinata a testare le API dei framework, quindi non è molto significativo per loro avere un pacchetto di applicazioni target specifico, diverso dallo 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 imballaggio. Nella maggior parte dei casi, l'opzione del file Blueprint basato su Soong è sufficiente. Per maggiori dettagli, consulta Configurazione di un test semplice.

File di configurazione complesso

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

La configurazione del test può specificare opzioni di configurazione del dispositivo speciali e argomenti predefinite per fornire la classe di test. Vedi l'esempio su /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>

Alcune osservazioni specifiche 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 indica alla Trade Federation di installare HelloWorldTests.apk sul dispositivo di destinazione utilizzando un target_preparer specificato. In Trade Federation sono disponibili molti preparatisti di target per gli sviluppatori, che possono essere utilizzati per assicurarsi 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 della Trade Federation da utilizzare per eseguire il test e supera il pacchetto del dispositivo da eseguire, nonché il framework del runner del test, in questo caso JUnit.

Per ulteriori informazioni, consulta Test Config Modu.

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 gerrit di esempio contiene alcuni utilizzi molto di base delle sue funzionalità. Consulta 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 utili in generale.

@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, si scrivono test in classi Java semplici e si utilizzano l'annotazione per indicare determinati vincoli e configurazione di test. In questo esempio, dichiariamo che questo corso deve essere eseguito 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 il teardown post test. Analogamente, le annotazioni @BeforeClass e @AfterClass vengono utilizzate sui metodi di JUnit4 per eseguire la configurazione prima di eseguire tutti i test in un test class e il teardown successivamente. Tieni presente che i metodi di configurazione e smontaggio a livello di 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 ogni metodo deve essere annotato con @Test. Come di consueto, i metodi di test devono essere pubblici, dichiarare nessun valore restituito, non accettare parametri e possono generare eccezioni.

Accesso alla classe di misurazione

Sebbene non sia trattato nell'esempio di base Ciao mondo, è abbastanza comune che un test Android richieda un'istanza di accesso Instrumentation: si tratta dell'interfaccia API di base che fornisce l'accesso ai contesti dell'applicazione, alle API di test relative al ciclo di vita dell'attività e altro ancora.

Poiché i test JUnit4 non richiedono più una classe di base comune, non è più necessario ottenere un'istanza Instrumentation tramite InstrumentationTestCase#getInstrumentation(), ma il nuovo test runner la gestisce tramite InstrumentationRegistry, dove viene memorizzata la configurazione contestuale e ambientale creata dal framework di misurazione.

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

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Crea e testa in locale

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

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