Szukaj…


Wprowadzenie

Celem tego dokumentu jest przedstawienie celów i sposobów pisania interfejsu użytkownika Androida i testów integracyjnych. Google zapewnia espresso i UIAutomator, więc należy skupić się na tych narzędziach i ich opakowaniach, np. Appium, Spoon itp.

Składnia

  • Zasób na biegu jałowym
  • String getName () - Zwraca nazwę zasobu na biegu jałowym (używanego do rejestrowania i idempotency rejestracji).
  • boolean isIdleNow () - Zwraca true, jeśli zasób jest obecnie w stanie bezczynności.
  • void registerIdleTransitionCallback (IdlingResource.ResourceCallback callback) - Rejestruje podany IdlingResource.ResourceCallback z zasobem

Uwagi

Zasady JUnit:

Jak widać w przykładzie MockWebServer i ActivityTestRule, wszystkie one należą do kategorii reguł JUnit, które możesz sam stworzyć, które następnie należy wykonać dla każdego testu określającego jego zachowanie @ patrz: https://github.com/junit-team/junit4/ wiki / rules

Appium

Parametry

Ponieważ parametry mają pewne problemy z umieszczeniem ich tutaj, dopóki błąd dokumentacji nie zostanie rozwiązany:

Parametr Detale
Aktywność klasowa Klasa które działanie rozpocząć
initialTouchMode czy aktywność powinna zostać umieszczona w trybie dotykowym na starcie: https://android-developers.blogspot.de/2008/12/touch-mode.html
launchActivity true, jeśli działanie należy uruchomić raz dla każdej metody testowej. Zostanie uruchomiony przed pierwszą metodą Before i zakończony po ostatniej metodzie After.

Przykład MockWebServer

W przypadku, gdy twoje działania, fragmenty i interfejs użytkownika wymagają przetwarzania w tle, dobrym rozwiązaniem jest MockWebServer, który działa lokalnie na urządzeniu z Androidem, co zapewnia zamknięte i testowalne środowisko dla twojego interfejsu użytkownika.

https://github.com/square/okhttp/tree/master/mockwebserver

Pierwszym krokiem jest uwzględnienie zależności stopnia:

testCompile 'com.squareup.okhttp3:mockwebserver:(insert latest version)'

Teraz kroki do uruchomienia i używania fałszywego serwera to:

  • utwórz próbny obiekt serwera
  • uruchom go pod określonym adresem i portem (zwykle localhost: numer_portu)
  • kolejkować odpowiedzi na konkretne żądania
  • rozpocznij test

Jest to ładnie wyjaśnione na stronie github na serwerze mockwebserver, ale w naszym przypadku chcemy czegoś ładniejszego i do wielokrotnego użytku dla wszystkich testów, a zasady JUnit będą tu dobrze się bawić:

/**
 *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”);
        }
    }
}

Załóżmy teraz, że mamy dokładnie taką samą aktywność jak w poprzednim przykładzie, właśnie w tym przypadku, gdy naciśniemy przycisk, aplikacja pobierze coś z sieci, na przykład: https://someapi.com/name

Zwróci to ciąg tekstowy, który zostanie połączony z tekstem paska przekąskowego, np. NAME + tekst, który wpisałeś.

/**
* 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;
    }

Sugeruję zawinięcie programu rozsyłającego w jakiegoś Konstruktora, aby można było łatwo łączyć i dodawać nowe odpowiedzi do ekranów. na przykład

 return newDispatcherBuilder()
            .withSerializedJSONBody("/authenticate", Mocks.getAuthenticationResponse())
            .withSerializedJSONBody("/getUserInfo", Mocks.getUserInfo())
            .withSerializedJSONBody("/checkNotBot", Mocks.checkNotBot());

IdlingResource

Moc bezczynnych zasobów polega na tym, że nie trzeba czekać na przetwarzanie niektórych aplikacji (tworzenie sieci, obliczenia, animacje itp.) Za pomocą funkcji sleep() , co powoduje niestabilność i / lub wydłuża przebieg testów. Oficjalna dokumentacja znajduje się tutaj .

Realizacja

Podczas implementacji interfejsu IdlingResource należy wykonać trzy czynności:

  • getName() - Zwraca nazwę twojego zasobu na biegu jałowym.
  • isIdleNow() - Sprawdza, czy Twój obiekt xyz, operacja itp. jest w tej chwili bezczynny.
  • registerIdleTransitionCallback ( IdlingResource.ResourceCallback callback) - Zapewnia wywołanie zwrotne, które należy wywołać, gdy obiekt przechodzi w stan bezczynności.

Teraz powinieneś stworzyć własną logikę i określić, kiedy aplikacja jest bezczynna, a kiedy nie, ponieważ zależy to od aplikacji. Poniżej znajdziesz prosty przykład, aby pokazać, jak to działa. Istnieją inne przykłady online, ale konkretna implementacja aplikacji prowadzi do konkretnych implementacji zasobów na biegu jałowym.

UWAGI

  • Było kilka przykładów Google, w których umieścili IdlingResources w kodzie aplikacji. Nie rób tego. Prawdopodobnie umieścili go tam, aby pokazać, jak działają.
  • Utrzymanie kodu w czystości i przestrzeganie jednej zasady odpowiedzialności zależy od Ciebie!

Przykład

Powiedzmy, że masz aktywność, która robi dziwne rzeczy i zajmuje dużo czasu, aby załadować fragment, a tym samym zawieść testy Espresso, ponieważ nie można znaleźć zasobów z tego fragmentu (powinieneś zmienić sposób, w jaki tworzona jest aktywność i kiedy przyspieszyć). Ale w każdym razie, aby to uprościć, poniższy przykład pokazuje, jak powinien on wyglądać.

Nasz przykładowy zasób na biegu jałowym otrzymałby dwa obiekty:

  • Tag fragmentu, który musisz znaleźć i czekać, aby dołączyć do działania.
  • Obiekt FragmentManager, który służy do znajdowania fragmentu.
/**
 * 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;
    }
}

Teraz, gdy masz już napisany IdlingResource , musisz go gdzieś użyć, prawda?

Stosowanie

Pomińmy całą konfigurację klasy testowej i zobaczmy, jak wyglądałby przypadek testowy:

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

Połączenie z regułą JUnit

To nie jest trudne; możesz także zastosować zasób biegu jałowego w formie reguły testowej JUnit. Powiedzmy na przykład, że masz pakiet SDK zawierający Volley i chcesz, aby Espresso na niego czekało. Zamiast analizować każdy przypadek testowy lub stosować go w konfiguracji, możesz utworzyć regułę JUnit i po prostu napisać:

@Rule
public final SDKIdlingRule mSdkIdlingRule = new SDKIdlingRule(SDKInstanceHolder.getInstance());

Ponieważ jest to przykład, nie bierz tego za pewnik; cały kod tutaj jest wymyślony i służy wyłącznie do celów demonstracyjnych:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow