Android
Att skriva UI-test - Android
Sök…
Introduktion
Fokus för detta dokument är att representera mål och sätt att skriva Android UI och integrationstester. Espresso och UIAutomator tillhandahålls av Google så fokus bör vara kring dessa verktyg och deras respektive omslag, t.ex. Appium, Spoon etc.
Syntax
- Tomgångsresurs
- String getName () - Returnerar namnet på tomgångsresursen (används för loggning och idempotens för registrering).
- boolean isIdleNow () - Returnerar sant om resursen för närvarande är inaktiv.
- void registerIdleTransitionCallback (IdlingResource.ResourceCallback callback) - Registrerar den givna IdlingResource.ResourceCallback med resursen
Anmärkningar
JUnit regler:
Som du kan se i MockWebServer-exempel och ActivityTestRule faller de alla under kategorin av JUnit-regler som du kan skapa själv som sedan ska utföras för varje test som definierar dess beteende @see: https://github.com/junit-team/junit4/ wiki / rules
Appium
parametrar
Eftersom parametrar har vissa problem att placera dem här tills dokumentfel har lösts:
Parameter | detaljer |
---|---|
KlassaktivitetKlass | vilken aktivitet som ska startas |
initialTouchMode | ska aktiviteten placeras i beröringsläge vid start: https://android-developers.blogspot.de/2008/12/touch-mode.html |
launchActivity | sant om aktiviteten bör startas en gång per testmetod. Den kommer att lanseras före den första före-metoden och avslutas efter den sista After-metoden. |
MockWebServer-exempel
Om dina aktiviteter, fragment och användargränssnitt kräver lite bakgrundsbehandling är en MockWebServer som körs lokalt på en Android-enhet som ger en stängd och testbar miljö för ditt användargränssnitt.
https://github.com/square/okhttp/tree/master/mockwebserver
Det första steget inkluderar gradenberoende:
testCompile 'com.squareup.okhttp3:mockwebserver:(insert latest version)'
Nu är steg för att köra och använda mock-servern:
- skapa spott serverobjekt
- starta den på specifik adress och port (vanligtvis localhost: portnummer)
- svar på svar för specifika förfrågningar
- starta testet
Detta förklaras snyggt på github-sidan för mockwebserver men i vårt fall vill vi ha något trevligare och återanvändbart för alla tester, och JUnit-regler kommer att komma bra in här:
/**
*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”);
}
}
}
Låt oss nu anta att vi har exakt samma aktivitet som i föregående exempel, just i det här fallet när vi trycker på knappen-appen hämtar något från nätverket till exempel: https://someapi.com/name
Detta skulle returnera en del textsträng som skulle sammanlänkas i snackbartexten, t.ex. NAME + text som du skrev in.
/**
* 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;
}
Jag föreslår att du lägger in dispatcherna i någon form av en Builder så att du enkelt kan kedja och lägga till nya svar för dina skärmar. t.ex
return newDispatcherBuilder()
.withSerializedJSONBody("/authenticate", Mocks.getAuthenticationResponse())
.withSerializedJSONBody("/getUserInfo", Mocks.getUserInfo())
.withSerializedJSONBody("/checkNotBot", Mocks.checkNotBot());
IdlingResource
Kraften i tomgångsresurser ligger i att inte behöva vänta på att någon apps bearbetning (nätverk, beräkningar, animationer osv.) Ska slutföras med sleep()
, vilket ger flakiness och / eller förlänger testkörningen. Den officiella dokumentationen kan hittas här .
Genomförande
Det finns tre saker du måste göra när du implementerar IdlingResource
gränssnittet:
-
getName()
- Returnerar namnet på din tomgångsresurs. -
isIdleNow()
- Kontrollerar om ditt xyz-objekt, drift etc. är inaktiv just nu. -
registerIdleTransitionCallback
(IdlingResource.ResourceCallback
) - Ger en återuppringning som du ska ringa när ditt objekt övergår till viloläge.
Nu bör du skapa din egen logik och bestämma när din app är inaktiv och när inte, eftersom det är beroende av appen. Nedan hittar du ett enkelt exempel bara för att visa hur det fungerar. Det finns andra exempel online, men specifik appimplementering ger specifika tomgångsresursimplementeringar.
OBSERVERA
- Det har funnits några Google-exempel där de placerade
IdlingResources
iIdlingResources
kod. Gör inte detta. De placerade förmodligen den där bara för att visa hur de fungerar. - Att hålla din kod ren och upprätthålla en enda princip om ansvar är upp till dig!
Exempel
Låt oss säga att du har en aktivitet som gör konstiga grejer och tar lång tid för fragmentet att ladda och därmed göra att dina Espresso-test misslyckas genom att inte kunna hitta resurser från ditt fragment (du bör ändra hur din aktivitet skapas och när för att påskynda det). Men för att hålla det enkelt visar följande exempel hur det ska se ut.
Vårt exempel på tomgångsresurs skulle få två objekt:
- Etiketten för fragmentet som du behöver hitta och väntar på att bli kopplad till aktiviteten.
- Ett FragmentManager- objekt som används för att hitta fragmentet.
/**
* 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 IdlingResource
du har skrivit din IdlingResource
måste du använda den någonstans, eller IdlingResource
?
Användande
Låt oss hoppa över hela testklassen och se hur ett testfall skulle se ut:
@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 med JUnit-regeln
Detta är inte så svårt; Du kan också tillämpa tomgångsresursen i form av en JUnit-testregel. Låt oss till exempel säga att du har en SDK som innehåller Volley i den och att du vill att Espresso ska vänta på det. Istället för att gå igenom varje testfall eller tillämpa det i installationen kan du skapa en JUnit-regel och bara skriva:
@Rule
public final SDKIdlingRule mSdkIdlingRule = new SDKIdlingRule(SDKInstanceHolder.getInstance());
Nu eftersom detta är ett exempel, ta det inte för givet; all kod här är imaginär och används endast för demonstrationsändamål:
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);
}
}
}
};
}
}