Android
Escribir pruebas de interfaz de usuario - Android
Buscar..
Introducción
El enfoque de este documento es representar objetivos y formas de escribir la interfaz de usuario de Android y las pruebas de integración. Google proporciona el expreso y el UIAutomator, por lo que el enfoque debe estar en torno a estas herramientas y sus respectivos envoltorios, por ejemplo, Appium, Spoon, etc.
Sintaxis
- Recurso inactivo
- Cadena getName (): devuelve el nombre del recurso inactivo (utilizado para el registro y la idempotencia del registro).
- boolean isIdleNow (): devuelve true si el recurso está actualmente inactivo.
- void registerIdleTransitionCallback (IdlingResource.ResourceCallback callback): registra el IdlingResource.ResourceCallback dado con el recurso
Observaciones
Reglas de JUnit:
Como puede ver en el ejemplo de MockWebServer y en ActivityTestRule, todas se incluyen en la categoría de reglas JUnit que puede crear usted mismo, que luego deben ejecutarse para cada prueba que defina su comportamiento @see: https://github.com/junit-team/junit4/ wiki / rules
Apio
Parámetros
Dado que los parámetros tienen algunos problemas al colocarlos aquí hasta que se resuelva el error de documentación:
Parámetro | Detalles |
---|---|
Actividad de clase clase | que actividad comenzar |
initialTouchMode | en caso de que la actividad se coloque en modo táctil al inicio: https://android-developers.blogspot.de/2008/12/touch-mode.html |
launchActivity | Es cierto si la Actividad debe iniciarse una vez por método de prueba. Se lanzará antes del primer método Antes, y terminará después del último método Después. |
Ejemplo de MockWebServer
En caso de que sus actividades, fragmentos e IU requieran algo de procesamiento en segundo plano, una buena cosa para usar es un MockWebServer que se ejecuta localmente en un dispositivo Android que brinda un entorno cerrado y comprobable para su IU.
https://github.com/square/okhttp/tree/master/mockwebserver
El primer paso es incluir la dependencia de Gradle:
testCompile 'com.squareup.okhttp3:mockwebserver:(insert latest version)'
Ahora los pasos para ejecutar y usar el servidor simulado son:
- crear un objeto de servidor simulado
- inícielo en la dirección y el puerto específicos (generalmente localhost: número de puerto)
- Encolar respuestas para solicitudes específicas
- comienza la prueba
Esto está muy bien explicado en la página github de mockwebserver pero en nuestro caso queremos algo mejor y reutilizable para todas las pruebas, y las reglas de JUnit entrarán en juego aquí:
/**
*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”);
}
}
}
Ahora asumamos que tenemos exactamente la misma actividad que en el ejemplo anterior, solo en este caso cuando presionamos el botón, la aplicación buscará algo de la red, por ejemplo: https://someapi.com/name
Esto devolvería algunas cadenas de texto que se concatenarían en el texto de la barra de snack, por ejemplo, el NOMBRE + texto que ingresaste.
/**
* 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;
}
Yo sugeriría envolver al despachador en una especie de Builder para que pueda encadenar fácilmente y agregar nuevas respuestas para sus pantallas. p.ej
return newDispatcherBuilder()
.withSerializedJSONBody("/authenticate", Mocks.getAuthenticationResponse())
.withSerializedJSONBody("/getUserInfo", Mocks.getUserInfo())
.withSerializedJSONBody("/checkNotBot", Mocks.checkNotBot());
IdlingResource
El poder de los recursos de inactividad radica en no tener que esperar a que el procesamiento de algunas aplicaciones (redes, cálculos, animaciones, etc.) finalice con el modo de sleep()
, lo que trae la descamación y prolonga las pruebas. La documentación oficial se puede encontrar aquí .
Implementación
Hay tres cosas que debe hacer al implementar la interfaz IdlingResource
:
-
getName()
: devuelve el nombre de su recurso inactivo. -
isIdleNow()
: comprueba si su objeto xyz, operación, etc. está inactivo en este momento. -
registerIdleTransitionCallback
(IdlingResource.ResourceCallback
callback): proporciona una devolución de llamada que debe llamar cuando su objeto pase al estado inactivo.
Ahora debe crear su propia lógica y determinar cuándo su aplicación está inactiva y cuándo no, ya que esto depende de la aplicación. A continuación encontrará un ejemplo simple, solo para mostrar cómo funciona. Hay otros ejemplos en línea, pero la implementación de una aplicación específica lleva a implementaciones específicas de recursos inactivos.
NOTAS
- Ha habido algunos ejemplos de Google donde pusieron
IdlingResources
en el código de la aplicación. No hagas esto. Presumiblemente lo colocaron allí solo para mostrar cómo funcionan. - ¡Mantener su código limpio y mantener un solo principio de responsabilidad depende de usted!
Ejemplo
Digamos que tiene una actividad que hace cosas extrañas y demora mucho tiempo en cargar el fragmento y, por lo tanto, hace que sus pruebas de Espresso fracasen al no poder encontrar recursos de su fragmento (debe cambiar cómo se crea su actividad y cuándo). para acelerarlo). Pero en cualquier caso, para mantenerlo simple, el siguiente ejemplo muestra cómo debería ser.
Nuestro ejemplo de recurso inactivo obtendría dos objetos:
- La etiqueta del fragmento que necesita encontrar y en espera de unirse a la actividad.
- Un objeto FragmentManager que se utiliza para encontrar el fragmento.
/**
* 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;
}
}
Ahora que tienes tu IdlingResource
escrito, necesitas usarlo en algún lugar, ¿no?
Uso
Vamos a omitir la configuración completa de la clase de prueba y solo veremos cómo se vería un caso de prueba:
@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);
}
Combinación con regla JUnit
Esto no es difícil; También puede aplicar el recurso inactivo en forma de una regla de prueba JUnit. Por ejemplo, digamos que tiene algún SDK que contiene Volley y quiere que Espresso lo espere. En lugar de pasar por cada caso de prueba o aplicarlo en la configuración, puede crear una regla JUnit y simplemente escribir:
@Rule
public final SDKIdlingRule mSdkIdlingRule = new SDKIdlingRule(SDKInstanceHolder.getInstance());
Ahora, ya que este es un ejemplo, no lo des por sentado; Todo el código aquí es imaginario y se usa solo para fines de demostración:
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);
}
}
}
};
}
}