Android
UI-tests schrijven - Android
Zoeken…
Invoering
De focus van dit document is het weergeven van doelen en manieren om Android UI- en integratietests te schrijven. Espresso en UIAutomator worden geleverd door Google, dus de nadruk moet liggen op deze tools en hun respectieve verpakkingen, bijvoorbeeld Appium, Spoon etc.
Syntaxis
- Bron voor stationair draaien
- String getName () - Retourneert de naam van de idling-resource (gebruikt voor logboekregistratie en idemoten van registratie).
- boolean isIdleNow () - Retourneert true als de resource momenteel niet actief is.
- void registerIdleTransitionCallback (IdlingResource.ResourceCallback callback) - Registreert de gegeven IdlingResource.ResourceCallback bij de resource
Opmerkingen
JUnit-regels:
Zoals u kunt zien in het voorbeeld van MockWebServer en ActivityTestRule vallen ze allemaal onder de categorie JUnit-regels die u zelf kunt maken en die vervolgens moet worden uitgevoerd voor elke test die het gedrag definieert @see: https://github.com/junit-team/junit4/ wiki / rules
Appium
parameters
Aangezien parameters problemen hebben om ze hier te plaatsen totdat de documentatie-bug is opgelost:
Parameter | Details |
---|---|
Klasse activiteit Klasse | welke activiteit om te beginnen |
initialTouchMode | moet de activiteit bij het starten in de aanraakmodus worden geplaatst: https://android-developers.blogspot.de/2008/12/touch-mode.html |
launchActivity | waar als de activiteit eenmaal per testmethode moet worden gestart. Het wordt gestart vóór de eerste methode Before en beëindigd na de laatste methode After. |
MockWebServer voorbeeld
In het geval dat uw activiteiten, fragmenten en UI enige achtergrondverwerking vereisen, is een goede zaak om te gebruiken een MockWebServer die lokaal op een Android-apparaat draait en een gesloten en testbare omgeving voor uw UI oplevert.
https://github.com/square/okhttp/tree/master/mockwebserver
De eerste stap is het opnemen van de gradele afhankelijkheid:
testCompile 'com.squareup.okhttp3:mockwebserver:(insert latest version)'
Stappen voor het uitvoeren en gebruiken van de mock-server zijn nu:
- maak een mock-serverobject
- start het op specifiek adres en poort (meestal localhost: poortnummer)
- wachtrijantwoorden voor specifieke verzoeken
- start de test
Dit wordt mooi uitgelegd op de github-pagina van de mockwebserver, maar in ons geval willen we iets leukers en herbruikbaar voor alle tests, en JUnit-regels komen hier goed van pas:
/**
*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”);
}
}
}
Laten we nu aannemen dat we exact dezelfde activiteit hebben als in het vorige voorbeeld, alleen in dit geval wanneer we op de knop-app drukken, wordt er bijvoorbeeld iets uit het netwerk opgehaald: https://someapi.com/name
Dit zou een tekststring opleveren die zou worden samengevoegd in de snackbartekst, bijvoorbeeld NAME + tekst die u hebt ingevoerd.
/**
* 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;
}
Ik zou willen voorstellen om de dispatcher in een soort Builder te verpakken, zodat je eenvoudig nieuwe antwoorden voor je schermen kunt koppelen en toevoegen. bv
return newDispatcherBuilder()
.withSerializedJSONBody("/authenticate", Mocks.getAuthenticationResponse())
.withSerializedJSONBody("/getUserInfo", Mocks.getUserInfo())
.withSerializedJSONBody("/checkNotBot", Mocks.checkNotBot());
IdlingResource
De kracht van inactieve bronnen ligt in het niet hoeven te wachten op de verwerking van sommige apps (netwerken, berekeningen, animaties, enz.) Om te eindigen met sleep()
, wat schilfering met zich meebrengt en / of de testruns verlengt. De officiële documentatie vindt u hier .
Implementatie
Er zijn drie dingen die u moet doen bij het implementeren van de IdlingResource
interface:
-
getName()
- Retourneert de naam van uw inactieve bron. -
isIdleNow()
- Controleert of uw xyz-object, bewerking, enz. momenteel inactief is. -
registerIdleTransitionCallback
(IdlingResource.ResourceCallback
callback) - Biedt een callback die u moet aanroepen wanneer uw object overgaat naar inactiviteit.
Nu moet u uw eigen logica maken en bepalen wanneer uw app inactief is en wanneer niet, omdat dit afhankelijk is van de app. Hieronder vindt u een eenvoudig voorbeeld om te laten zien hoe het werkt. Er zijn andere voorbeelden online, maar specifieke app-implementatie zorgt voor specifieke implementaties van niet-actieve bronnen.
OPMERKINGEN
- Er zijn enkele Google-voorbeelden geweest waarin ze
IdlingResources
in de code van de app hebben geplaatst. Doe dit niet. Ze hebben het vermoedelijk daar geplaatst om te laten zien hoe ze werken. - Het is aan jou om je code schoon te houden en één verantwoordelijkheidsbeginsel te handhaven!
Voorbeeld
Laten we zeggen dat je een activiteit hebt die rare dingen doet en het lang duurt voordat het fragment is geladen en waardoor je Espresso-tests mislukken door geen bronnen van je fragment te vinden (je moet veranderen hoe je activiteit wordt gemaakt en wanneer om het te versnellen). Maar om het simpel te houden, laat het volgende voorbeeld zien hoe het eruit zou moeten zien.
Onze voorbeeld-idling-resource zou twee objecten krijgen:
- De tag van het fragment dat je moet vinden en wacht om gehecht te raken aan de activiteit.
- Een FragmentManager- object dat wordt gebruikt om het fragment te vinden.
/**
* 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;
}
}
Nu u uw IdlingResource
geschreven, moet u deze ergens gebruiken, toch?
Gebruik
Laten we de hele setup van de testklasse overslaan en kijken hoe een testcase eruit zou zien:
@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);
}
Combinatie met JUnit-regel
Dit is niet zo moeilijk; u kunt de bron voor stationair draaien ook toepassen in de vorm van een JUnit-testregel. Laten we bijvoorbeeld zeggen dat u een SDK hebt die Volley bevat en dat u wilt dat Espresso erop wacht. In plaats van elke testcase te doorlopen of in de setup toe te passen, kunt u een JUnit-regel maken en gewoon schrijven:
@Rule
public final SDKIdlingRule mSdkIdlingRule = new SDKIdlingRule(SDKInstanceHolder.getInstance());
Aangezien dit een voorbeeld is, neem het niet als vanzelfsprekend aan; alle code hier is denkbeeldig en wordt alleen gebruikt voor demonstratiedoeleinden:
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);
}
}
}
};
}
}