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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow