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 i IdlingResources 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);
                    }
                }
            }
        };
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow