Ricerca…


introduzione

Focus di questo documento è quello di rappresentare obiettivi e modi in cui scrivere UI di Android e test di integrazione. Espresso e UIAutomator sono forniti da Google, pertanto è necessario concentrarsi su questi strumenti e sui rispettivi wrapper, ad esempio Appium, Spoon ecc.

Sintassi

  • Risorsa al minimo
  • String getName () - Restituisce il nome della risorsa inattiva (utilizzata per la registrazione e l'idempotenza della registrazione).
  • booleano isIdleNow () - Restituisce vero se la risorsa è al momento inattiva.
  • void registerIdleTransitionCallback (IdlingResource.ResourceCallback callback) - Registra il dato IdlingResource.ResourceCallback con la risorsa

Osservazioni

Regole di JUnit:

Come puoi vedere nell'esempio MockWebServer e ActivityTestRule rientrano tutti nella categoria di regole JUnit che puoi creare tu stesso che poi dovrebbero essere eseguite per ogni test definendo il suo comportamento @see: https://github.com/junit-team/junit4/ wiki / regole

Appium

parametri

Poiché i parametri presentano alcuni problemi, posizionali qui finché il bug della documentazione non viene risolto:

Parametro Dettagli
Class activityClass quale attività iniziare
initialTouchMode se l'attività viene posizionata in modalità touch all'avvio: https://android-developers.blogspot.de/2008/12/touch-mode.html
launchActivity vero se l'attività deve essere avviata una volta per metodo di prova. Verrà avviato prima del primo metodo Before e terminato dopo l'ultimo metodo After.

Esempio di MockWebServer

Nel caso in cui le tue attività, i frammenti e l'interfaccia utente richiedano qualche elaborazione in background, una cosa buona da usare è un MockWebServer che gira localmente su un dispositivo Android che porta un ambiente chiuso e testabile per l'interfaccia utente.

https://github.com/square/okhttp/tree/master/mockwebserver

Il primo passo è includere la dipendenza gradle:

testCompile 'com.squareup.okhttp3:mockwebserver:(insert latest version)'

Ora i passaggi per l'esecuzione e l'utilizzo del server di simulazione sono:

  • creare un oggetto server fittizio
  • avviarlo all'indirizzo e alla porta specifici (in genere localhost: portnumber)
  • accodare le risposte per richieste specifiche
  • inizia il test

Questo è ben spiegato nella pagina github del mockwebserver, ma nel nostro caso vogliamo qualcosa di più bello e riusabile per tutti i test, e le regole di JUnit saranno ben giocate qui:

/**
 *JUnit  rule that starts and stops a mock web server for test runner
*/
 public class MockServerRule extends UiThreadTestRule {

 private MockWebServer mServer;

 public static final int MOCK_WEBSERVER_PORT = 8000;

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                startServer();
                try {
                    base.evaluate();
                } finally {
                    stopServer();
                }
            }
        };
    }

    /**
     * Returns the started web server instance
     *
     * @return mock server
     */
    public MockWebServer server() {
        return mServer;
    }

    public void startServer() throws IOException, NoSuchAlgorithmException {
        mServer = new MockWebServer();
        try {
            mServer(MOCK_WEBSERVER_PORT);
        } catch (IOException e) {
            throw new IllegalStateException(e,"mock server start issue");
        }
    }

    public void stopServer() {
        try {
            mServer.shutdown();
        } catch (IOException e) {
            Timber.e(e, "mock server shutdown error”);
        }
    }
}

Ora supponiamo di avere la stessa identica attività come nell'esempio precedente, solo in questo caso quando spingiamo l'app pulsante recupereremo qualcosa dalla rete, ad esempio: https://someapi.com/name

Ciò restituirebbe una stringa di testo che verrebbe concatenata nel testo dello snackbar, ad esempio NOME + testo digitato.

/**
* Testing of the snackbar activity with networking.
**/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class SnackbarActivityTest{
    //espresso rule which tells which activity to start
    @Rule
    public final ActivityTestRule<SnackbarActivity> mActivityRule = 
        new ActivityTestRule<>(SnackbarActivity.class, true, false);

    //start mock web server
    @Rule
    public final MockServerRule mMockServerRule = new MockServerRule();

    @Override
    public void tearDown() throws Exception {
       //same as previous example
    }
    
    @Override
    public void setUp() throws Exception {
       //same as previous example

       **//IMPORTANT:** point your application to your mockwebserver endpoint e.g.
       MyAppConfig.setEndpointURL("http://localhost:8000");
    }
    
    /**
    *Test methods should always start with "testXYZ" and it is a good idea to 
    *name them after the intent what you want to test
    **/
    @Test
    public void testSnackbarIsShown() {
        //setup mockweb server
        mMockServerRule.server().setDispatcher(getDispatcher());

        mActivityRule.launchActivity(null);
        //check is our text entry displayed and enter some text to it
        String textToType="new snackbar text";
        onView(withId(R.id.textEntry)).check(matches(isDisplayed()));
        //we check is our snackbar showing text from mock webserver plus the one we typed
        onView(withId(R.id.textEntry)).perform(typeText("JazzJackTheRabbit" + textToType));
        //click the button to show the snackbar
        onView(withId(R.id.shownSnackbarBtn)).perform(click());
        //assert that a view with snackbar_id with text which we typed and is displayed
        onView(allOf(withId(android.support.design.R.id.snackbar_text), 
        withText(textToType))) .check(matches(isDisplayed()));
    }
    
     /**
     *creates a mock web server dispatcher with prerecorded requests and responses
     **/
    private Dispatcher getDispatcher() {
        final Dispatcher dispatcher = new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
                if (request.getPath().equals("/name")){
                    return new MockResponse().setResponseCode(200)
                            .setBody("JazzJackTheRabbit");
                }
                throw new IllegalStateException("no mock set up for " + request.getPath());
            }
        };
        return dispatcher;
    }

Vorrei suggerire di avvolgere il dispatcher in una sorta di Builder in modo da poter facilmente concatenare e aggiungere nuove risposte per i tuoi schermi. per esempio

 return newDispatcherBuilder()
            .withSerializedJSONBody("/authenticate", Mocks.getAuthenticationResponse())
            .withSerializedJSONBody("/getUserInfo", Mocks.getUserInfo())
            .withSerializedJSONBody("/checkNotBot", Mocks.checkNotBot());

IdlingResource

Il potere delle risorse inattive consiste nel non dover attendere l'elaborazione di alcune app (networking, calcoli, animazioni, ecc.) Per finire con sleep() , che porta alla falla e / o prolunga i test eseguiti. La documentazione ufficiale può essere trovata qui .

Implementazione

Ci sono tre cose che devi fare quando si implementa IdlingResource interfaccia IdlingResource :

  • getName() - Restituisce il nome della risorsa inattiva.
  • isIdleNow() - Controlla se il tuo oggetto xyz, operazione, ecc. è inattivo al momento.
  • registerIdleTransitionCallback ( IdlingResource.ResourceCallback callback) - Fornisce un callback da chiamare quando il tuo oggetto passa in standby.

Ora dovresti creare la tua logica e determinare quando la tua app è inattiva e quando no, dato che dipende dall'app. Di seguito troverai un semplice esempio, giusto per mostrare come funziona. Esistono altri esempi online, ma l'implementazione specifica di app porta a specifiche implementazioni di risorse inattive.

GLI APPUNTI

  • Ci sono stati alcuni esempi di Google in cui hanno inserito IdlingResources nel codice dell'app. Non farlo. Presumibilmente l'hanno messo lì solo per mostrare come funzionano.
  • Mantenere il proprio codice pulito e mantenere un unico principio di responsabilità dipende da voi!

Esempio

Diciamo che hai un'attività che fa cose strane e richiede molto tempo per il caricamento del frammento e quindi i test Espresso falliscono non potendo trovare risorse dal tuo frammento (dovresti cambiare il modo in cui la tua attività viene creata e quando per accelerarlo). Ma in ogni caso per mantenerlo semplice, l'esempio seguente mostra come dovrebbe essere.

Il nostro esempio di risorsa inattiva otterrebbe due oggetti:

  • Il tag del frammento che devi trovare e in attesa di essere collegato all'attività.
  • Un oggetto FragmentManager che viene utilizzato per trovare il frammento.
/**
 * FragmentIdlingResource - idling resource which waits while Fragment has not been loaded.
 */
public class FragmentIdlingResource implements IdlingResource {
    private final FragmentManager mFragmentManager;
    private final String mTag;
    //resource callback you use when your activity transitions to idle
    private volatile ResourceCallback resourceCallback;

    public FragmentIdlingResource(FragmentManager fragmentManager, String tag) {
        mFragmentManager = fragmentManager;
        mTag = tag;
    }

    @Override
    public String getName() {
        return FragmentIdlingResource.class.getName() + ":" + mTag;
    }

    @Override
    public boolean isIdleNow() {
        //simple check, if your fragment is added, then your app has became idle
        boolean idle = (mFragmentManager.findFragmentByTag(mTag) != null);
        if (idle) {
            //IMPORTANT: make sure you call onTransitionToIdle
            resourceCallback.onTransitionToIdle();
        }
        return idle;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
}

Ora che hai scritto IdlingResource , devi usarlo da qualche parte, giusto?

uso

Saltiamo l'intera configurazione della classe di test e guardiamo come apparirebbe un caso di test:

@Test
public void testSomeFragmentText() {
    mActivityTestRule.launchActivity(null);
   
    //creating the idling resource
    IdlingResource fragmentLoadedIdlingResource = new FragmentIdlingResource(mActivityTestRule.getActivity().getSupportFragmentManager(), SomeFragmentText.TAG);
    //registering the idling resource so espresso waits for it
    Espresso.registerIdlingResources(idlingResource1);
    onView(withId(R.id.txtHelloWorld)).check(matches(withText(helloWorldText)));

    //lets cleanup after ourselves
    Espresso.unregisterIdlingResources(fragmentLoadedIdlingResource);
}

Combinazione con la regola JUnit

Questo non è difficile; puoi anche applicare la risorsa inattiva sotto forma di una regola di test JUnit. Ad esempio, diciamo che hai qualche SDK che contiene Volley e vuoi che Espresso lo aspetti. Invece di esaminare ogni caso di test o applicarlo in fase di configurazione, è possibile creare una regola JUnit e scrivere semplicemente:

@Rule
public final SDKIdlingRule mSdkIdlingRule = new SDKIdlingRule(SDKInstanceHolder.getInstance());

Ora, poiché questo è un esempio, non darlo per scontato; tutto il codice qui è immaginario e usato solo a scopo dimostrativo:

public class SDKIdlingRule implements TestRule {
    //idling resource you wrote to check is volley idle or not
    private VolleyIdlingResource mVolleyIdlingResource;
    //request queue that you need from volley to give it to idling resource
    private RequestQueue mRequestQueue;

    //when using the rule extract the request queue from your SDK
    public SDKIdlingRule(SDKClass sdkClass) {
        mRequestQueue = getVolleyRequestQueue(sdkClass);
    }

    private RequestQueue getVolleyRequestQueue(SDKClass sdkClass) {
        return sdkClass.getVolleyRequestQueue();
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                //registering idling resource
                mVolleyIdlingResource = new VolleyIdlingResource(mRequestQueue);
                Espresso.registerIdlingResources(mVolleyIdlingResource);
                try {
                    base.evaluate();
                } finally {
                    if (mVolleyIdlingResource != null) {
                        //deregister the resource when test finishes
                        Espresso.unregisterIdlingResources(mVolleyIdlingResource);
                    }
                }
            }
        };
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow