Android
Scrittura di test dell'interfaccia utente - Android
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);
}
}
}
};
}
}