Android
UI-Tests schreiben - Android
Suche…
Einführung
Der Schwerpunkt dieses Dokuments liegt auf der Darstellung von Zielen und Schreibweisen für Android-Benutzeroberflächen- und Integrationstests. Espresso und UIAutomator werden von Google bereitgestellt. Der Fokus sollte sich also auf diese Tools und ihre jeweiligen Verpackungen (z. B. Appium, Löffel usw.) richten.
Syntax
- Ressource im Leerlauf
- String getName () - Gibt den Namen der im Leerlauf befindlichen Ressource zurück (wird für die Protokollierung und die Idempotenz der Registrierung verwendet).
- boolean isIdleNow () - Gibt "true" zurück, wenn sich die Ressource aktuell im Leerlauf befindet.
- void registerIdleTransitionCallback (IdlingResource.ResourceCallback-Callback) - Registriert die angegebene IdlingResource.ResourceCallback bei der Ressource
Bemerkungen
JUnit-Regeln:
Wie Sie in MockWebServer Beispiel und ActivityTestRule sie alle fallen unter die Kategorie von JUnit Regeln sehen können , die Sie selbst erstellen können , die dann sollte für jeden Test definiert , sein Verhalten @see ausgeführt werden: https://github.com/junit-team/junit4/ Wiki / Regeln
Appium
Parameter
Da bei Parametern einige Probleme auftreten, setzen Sie sie hier, bis der Dokumentationsfehler behoben ist:
Parameter | Einzelheiten |
---|---|
KlassenaktivitätKlasse | welche Tätigkeit beginnt |
initialTouchMode | Sollte die Aktivität beim Start in den Touch-Modus versetzt werden: https://android-developers.blogspot.de/2008/12/touch-mode.html |
launchActivity | true, wenn die Aktivität einmal pro Testmethode gestartet werden soll. Es wird vor der ersten Before-Methode gestartet und nach der letzten After-Methode beendet. |
MockWebServer Beispiel
Falls für Ihre Aktivitäten, Fragmente und Benutzeroberflächen Hintergrundverarbeitung erforderlich ist, empfiehlt es sich, einen MockWebServer zu verwenden, der lokal auf einem Android-Gerät ausgeführt wird, das eine geschlossene und testbare Umgebung für Ihre Benutzeroberfläche bietet.
https://github.com/square/okhttp/tree/master/mockwebserver
Der erste Schritt umfasst die Abstufung der Abstufung:
testCompile 'com.squareup.okhttp3:mockwebserver:(insert latest version)'
Schritte zum Ausführen und Verwenden des Mock-Servers sind jetzt:
- Mock-Serverobjekt erstellen
- Starten Sie es an einer bestimmten Adresse und einem bestimmten Port (normalerweise localhost: Portnummer).
- Antworten für spezifische Anfragen in der Warteschlange
- Test starten
Das wird auf der Github-Seite des Mockwebservers gut erklärt, aber in unserem Fall möchten wir für alle Tests etwas Netteres und Wiederverwendbares, und JUnit-Regeln werden hier gut ins Spiel kommen:
/**
*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”);
}
}
}
Nehmen wir nun an, dass wir genau die gleiche Aktivität wie im vorigen Beispiel haben. In diesem Fall wird beim Drücken der Schaltfläche die App beispielsweise etwas vom Netzwerk abrufen: https://someapi.com/name
Dies würde eine Textzeichenfolge zurückgeben, die im Snackbar-Text verkettet würde, z. B. NAME + von Ihnen eingegebener Text.
/**
* 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;
}
Ich würde vorschlagen, den Dispatcher in eine Art Builder zu packen, damit Sie auf einfache Weise neue Antworten für Ihre Bildschirme hinzufügen können. z.B
return newDispatcherBuilder()
.withSerializedJSONBody("/authenticate", Mocks.getAuthenticationResponse())
.withSerializedJSONBody("/getUserInfo", Mocks.getUserInfo())
.withSerializedJSONBody("/checkNotBot", Mocks.checkNotBot());
IdlingResource
Die Leistung von Ressourcen im Leerlauf besteht darin, dass Sie nicht auf die Verarbeitung einiger Apps (Netzwerk, Berechnungen, Animationen usw.) warten müssen, um mit sleep()
enden, was Flockigkeit verursacht und / oder den Testlauf verlängert. Die offiziellen Unterlagen finden Sie hier .
Implementierung
Bei der Implementierung der IdlingResource
Schnittstelle müssen Sie drei Dinge tun:
-
getName()
- Gibt den Namen Ihrer im Leerlauf befindlichen Ressource zurück. -
isIdleNow()
- Überprüft, ob sich das Xyz-Objekt, die Operation usw. momentan im Leerlauf befindet. -
registerIdleTransitionCallback
(IdlingResource.ResourceCallback
Rückruf) - Stellt einen RückrufIdlingResource.ResourceCallback
den Sie aufrufen sollten, wenn Ihr Objekt in den LeerlaufIdlingResource.ResourceCallback
.
Jetzt sollten Sie Ihre eigene Logik erstellen und bestimmen, wann sich Ihre App im Leerlauf befindet und wann nicht, da dies von der App abhängt. Nachfolgend finden Sie ein einfaches Beispiel, um zu zeigen, wie es funktioniert. Es gibt andere Beispiele online, aber die Implementierung spezifischer Apps führt zu spezifischen Implementierungen von Ressourcen im Leerlauf.
ANMERKUNGEN
- Es gab einige Google-Beispiele, bei denen
IdlingResources
in den Code der AppIdlingResources
. Mach das nicht. Vermutlich haben sie es dort aufgestellt, um zu zeigen, wie sie arbeiten. - Es bleibt Ihnen überlassen, Ihren Code sauber zu halten und ein Prinzip der Verantwortung zu wahren!
Beispiel
Nehmen wir an, Sie haben eine Aktivität, die seltsame Sachen macht und das Laden des Fragments lange Zeit in Anspruch nimmt, sodass Ihre Espresso-Tests fehlschlagen, wenn Sie keine Ressourcen in Ihrem Fragment finden können (Sie sollten ändern, wie und wann Ihre Aktivität erstellt wird) um es zu beschleunigen). Aber um es einfach zu halten, zeigt das folgende Beispiel, wie es aussehen sollte.
Unsere Beispielressource für den Leerlauf würde zwei Objekte erhalten:
- Das Tag des Fragments, das Sie finden müssen und darauf warten, an die Aktivität angehängt zu werden.
- Ein FragmentManager- Objekt, das zum Suchen des Fragments verwendet wird.
/**
* 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;
}
}
Nun, da Sie Ihre IdlingResource
geschrieben haben, müssen Sie sie irgendwo richtig einsetzen?
Verwendungszweck
Lassen Sie uns die gesamte Testklasseneinrichtung überspringen und schauen Sie einfach, wie ein Testfall aussehen würde:
@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);
}
Kombination mit der JUnit-Regel
Das ist nicht zu schwer; Sie können die Leerlaufressource auch in Form einer JUnit-Testregel anwenden. Angenommen, Sie haben ein SDK mit Volley und möchten, dass Espresso darauf wartet. Anstatt jeden Testfall durchzusehen oder im Setup anzuwenden, können Sie eine JUnit-Regel erstellen und einfach Folgendes schreiben:
@Rule
public final SDKIdlingRule mSdkIdlingRule = new SDKIdlingRule(SDKInstanceHolder.getInstance());
Jetzt, da dies ein Beispiel ist, sollte es nicht als selbstverständlich gelten. Der gesamte Code hier ist imaginär und wird nur zu Demonstrationszwecken verwendet:
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);
}
}
}
};
}
}