UI Testing con EarlGrey 2: Parte 3
¡Hola Hola! Si es que llegaste hasta acá en búsqueda de lo que ofrecí en la primera entrega de esta serie de artículos, significa que ya has leído las otras 2 partes. Si aún no lo has hecho, puedes leerlas acá:
En el capítulo anterior de “UI Testing con EarlGrey2”
Resumidamente, en la segunda parte:
- Se preparó la aplicación de prueba agregando identificadores de accesibilidad.
- Se preparó el target de pruebas de UI para suplir algunas fallas de EarlGrey.
- Se escribió una prueba usando EarlGrey 2 y se explicó la misma en detalle.
En esta tercera parte finalmente habilitaré el soporte de pruebas de caja blanca y escribiré una prueba para ejemplificarlo.
Recuerda que todo lo que se avanzó en la segunda parte se encuentra en la rama black-box-tests
del repositorio que contiene el proyecto de ejemplo.
Pruebas de “caja ¿qué?”
A lo largo de esta serie de artículos he mencionado los conceptos de pruebas de caja negra y caja blanca ¿Pero cómo los aplicamos en el proyecto de ejemplo?
En la parte 2 implementé pruebas en donde el foco principal se centró en verificar que la interacción de un usuario funcione como se espera, dado cierto comportamiento.
¿Cómo la aplicación realiza los llamados a los servicios?
¿De qué forma son construidas las vistas y en qué momento se inyectan las dependencias?
¿Cuáles son las clases y estructuras de datos involucradas?
Todas estas preguntas no pueden ser respondidas, al menos en ese contexto. ¿Por qué? Esto se debe principalmente que las pruebas escritas usando EarlGrey 2 utilizan XCUITests
como base y se estructuran sobre en el concepto de caja negra, lo que implica en que no es posible acceder a la estructura interna del elemento a probar. En este caso, no es posible acceder al código fuente de la aplicación ni realizar modificaciones al mismo.
En este artículo, en cambio, se implementarán pruebas de caja blanca, las cuales sí toman en cuenta y utilizan la estructura interna del elemento a probar. Esto implica que las pruebas deberían poder acceder, por ejemplo, al código fuente de la aplicación y modificar su comportamiento.
¿Pero no que EarlGrey 2 se basa en
XCUITests
?¿Cómo puedes acceder a los elementos internos de la aplicación si tus pruebas utilizan una herramienta que no lo permite?
Es aquí donde la integración entre EarlGrey 2 y eDistantObject entra en escena.
En resumidas cuentas:
- Cada interacción realizada desde una prueba de EarlGrey es empaquetada en una invocación.
- La invocación es enviada desde la prueba hacia la aplicación, todo esto usando
eDistantObject
. - La aplicación procesa la invocación, realiza la acción que corresponda y devuelve la respuesta hacia la prueba.
Esto es utilizado por EarlGrey para poder realizar las interacciones y simular gestos de usuario sobre la aplicación, lo que hasta acá proporciona soporte completo para pruebas de caja negra. Sin embargo, también es posible realizar interacciones que involucren modificar elementos internos de la aplicación en tiempo de ejecución, es decir, lo que se espera de una prueba de caja blanca. Esto se verá en detalle, a continuación.
¡Comencemos!
Agregando soporte para pruebas de caja blanca
Para habilitar las pruebas de caja blanca es necesario crear un bundle, el cual será inyectado en el binario de la aplicación al momento de compilar el target de pruebas de UI. Esto se debe a que dentro de este bundle irá todo el código que permitirá comunicarnos con la aplicación desde la prueba que se ejecuta en ItunesSimpleSearchUITests
.
Además, es necesario aplicar algunas configuraciones a ItunesSimpleSearchUITests
para que tome el bundle que se creará e inyectarlo en la aplicación.
Configurando bundle
En primer lugar, es necesario crear un nuevo target de tipo bundle. Para ello:
- En Xcode:
File
->New
->Target
- Selecciona
macOS
->Bundle
- El nombre del bundle es
HelperBundle
Con el bundle creado, debemos hacer los siguientes cambios en la sección Build Settings
:
- En la sección
Enable Bitcode
seleccionaNo
. Base SDK
debe quedar configurado comoiOS
- Agregar
-ObjC
enOther Linker Flags
- Añadir
@loader_path/Frameworks
enRunpath Search Paths
- Agregar las siguientes variables en la sección
User Header Search Paths
seleccionandorecursive
:
$(SRCROOT)/EarlGrey2
$(SRCROOT)/EarlGrey2/Submodules/eDistantObject
- En la sección
Bundle Loader
agrega:
$(TARGET_BUILD_DIR)/ItunesSimpleSearch.app/ItunesSimpleSearch
En la sección Build Phases
debemos asignar las siguientes configuraciones:
- Agregar el target principal, es decir
ItunesSimpleSearch
en la secciónDependencies
- Agregar
AppFramework.framework
en la secciónLink Binary With Libraries
y dejar el campoStatus
comoOptional
Configurando target de pruebas de UI
En nuestro target ItunesSimpleSearchUITests
debemos integrar el bundle que se creó en el paso anterior, para ello debes hacer lo siguiente:
- Haz clic en el botón (+) y selecciona
New Copy Files Phase
. - Selecciona
Absolute Path
paraDestination
. - En la sección
Path
agrega:
$(TARGET_BUILD_DIR)/../../ItunesSimpleSearch.app/EarlGreyHelperBundles
Si sigues todos estos pasos, debería verse así:
Además, debes agregar la biblioteca SDWebImageSwiftUI
en la sección Link Binary With Libraries
En este punto ya tenemos las configuraciones necesarias para poder compilar la aplicación y las pruebas. Sin embargo al intentar correrlas te encontrarás con la siguiente pantalla:
Esto se debe a que como el bundle creado no tiene archivos no hay nada que cargar de forma dinámica y a EarlGrey no le gustan estas cosas. Sin embargo, no todo esta perdido. De hecho, a partir de acá comienza la mejor parte de la historia.
Conectando la aplicación con las pruebas de UI
Al fin ha llegado el momento, la hora de la verdad… por lo que has estado esperando desde hace dos artículos atrás. Al fin podrás saber cómo consultar y modificar elementos de la aplicación en tiempo de ejecución mientras corres una prueba de UI.
Dejando el hype aparte (si es que existe) lo primero que se debe hacer es definir que elementos obtener o modificar de la aplicación. Para ello, lo que recomienda la documentación de EarlGrey para swift es utilizar un protocolo y definir allí los elementos a consultar e inyectar. En este caso, lo que haré será configurar una inyección de dependencias para simular diferentes respuestas que podría entregar la API de iTunes. Todo esto se hará como prometí, en tiempo de ejecución y desde el target de pruebas de UI.
Creando las bases de comunicación
En primer lugar debes crear un archivo llamado SwiftTestHost.swift
en HelperBundle
como se muestra a continuación:
Al momento de crear este archivo ten en cuenta lo siguiente:
- Selecciona tanto
HelperBundle
comoItunesSimpleSearchUITest
en la sección Targets. - Si te Xcode te ofrece crear un archivo de Bridging Header, selecciona
Create Bridging Header
. Esto creará el archivoHelperBundle-Birgding-Header.h
en el que debes agregar las siguientes cabeceras:
#import "AppFramework/Action/GREYAction.h"
#import "AppFramework/Action/GREYActionBlock.h"
#import "AppFramework/Action/GREYActions.h"
#import "CommonLib/DistantObject/GREYHostApplicationDistantObject.h"
#import "CommonLib/Matcher/GREYElementMatcherBlock.h"
#import "CommonLib/Matcher/GREYMatcher.h"
Luego, agrega lo siguiente a SwiftTestHost.swift
:
Los métodos definidos en el protocolo ya dan una idea de la forma en la que se hará la inyección de dependencias. Veremos la implementación del mismo en detalle más adelante.
Agregando Mocks
Crea un grupo llamado Mock
en HelperBundle
y agrega los siguientes archivos:
JSONHelper.swift
con la lógica para cargar archivos JSON desdeHelperBundle
MockLookupRepository.swift
con la lógica de simular una respuesta/error de servicio:
lookup.json
con el contenido de la respuesta de servicio:
{
"resultCount": 1,
"results": [
{
"wrapperType": "track",
"kind": "song",
"artistId": 434832774,
"collectionId": 804145376,
"trackId": 804145419,
"artistName": "Romeo Santos",
"collectionName": "Fórmula, Vol. 2 (Deluxe Edition)",
"trackName": "Eres Mía",
"collectionCensoredName": "Fórmula, Vol. 2 (Deluxe Edition)",
"trackCensoredName": "Eres Mía",
"artistViewUrl": "https://itunes.apple.com/us/artist/romeo-santos/434832774?uo=4",
"collectionViewUrl": "https://itunes.apple.com/us/album/eres-m%C3%ADa/804145376?i=804145419&uo=4",
"trackViewUrl": "https://itunes.apple.com/us/album/eres-m%C3%ADa/804145376?i=804145419&uo=4",
"previewUrl": "https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/Music/v4/27/34/e1/2734e191-351e-3476-07fb-2c1b9f1d2576/mzaf_6415530891726922440.plus.aac.p.m4a",
"artworkUrl30": "https://is1-ssl.mzstatic.com/image/thumb/Music4/v4/19/41/22/194122c7-fee7-f5ba-4fc2-8cf130611faf/source/30x30bb.jpg",
"artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Music4/v4/19/41/22/194122c7-fee7-f5ba-4fc2-8cf130611faf/source/60x60bb.jpg",
"artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Music4/v4/19/41/22/194122c7-fee7-f5ba-4fc2-8cf130611faf/source/100x100bb.jpg",
"collectionPrice": 13.99,
"trackPrice": 1.29,
"releaseDate": "2014-02-25T08:00:00Z",
"collectionExplicitness": "explicit",
"trackExplicitness": "notExplicit",
"discCount": 1,
"discNumber": 1,
"trackCount": 19,
"trackNumber": 6,
"trackTimeMillis": 250640,
"country": "USA",
"currency": "USD",
"primaryGenreName": "Música tropical",
"isStreamable": true
}
]
}
lookup-2.json
con lo siguiente:
{
"resultCount": 1,
"results": [
{
"wrapperType": "track",
"kind": "song",
"artistId": 434832774,
"collectionId": 1258804404,
"trackId": 1258805162,
"artistName": "Romeo Santos, Daddy Yankee & Nicky Jam",
"collectionName": "Golden",
"trackName": "Bella y Sensual",
"collectionCensoredName": "Golden",
"trackCensoredName": "Bella y Sensual",
"collectionArtistName": "Romeo Santos",
"artistViewUrl": "https://itunes.apple.com/us/artist/romeo-santos/434832774?uo=4",
"collectionViewUrl": "https://itunes.apple.com/us/album/bella-y-sensual/1258804404?i=1258805162&uo=4",
"trackViewUrl": "https://itunes.apple.com/us/album/bella-y-sensual/1258804404?i=1258805162&uo=4",
"previewUrl": "https://audio-ssl.itunes.apple.com/apple-assets-us-std-000001/AudioPreview128/v4/74/c9/02/74c90240-46ce-4127-7347-15de726d377f/mzaf_4771328211351455789.plus.aac.p.m4a",
"artworkUrl30": "https://is4-ssl.mzstatic.com/image/thumb/Music118/v4/7e/fc/5b/7efc5b10-043b-c068-4c02-8a6dabba5cd4/source/30x30bb.jpg",
"artworkUrl60": "https://is4-ssl.mzstatic.com/image/thumb/Music118/v4/7e/fc/5b/7efc5b10-043b-c068-4c02-8a6dabba5cd4/source/60x60bb.jpg",
"artworkUrl100": "https://is4-ssl.mzstatic.com/image/thumb/Music118/v4/7e/fc/5b/7efc5b10-043b-c068-4c02-8a6dabba5cd4/source/100x100bb.jpg",
"collectionPrice": 12.99,
"trackPrice": 1.29,
"releaseDate": "2017-07-21T07:00:00Z",
"collectionExplicitness": "explicit",
"trackExplicitness": "notExplicit",
"discCount": 1,
"discNumber": 1,
"trackCount": 18,
"trackNumber": 10,
"trackTimeMillis": 204701,
"country": "USA",
"currency": "USD",
"primaryGenreName": "Música tropical",
"isStreamable": true
}
]
}
Creando la magia
Este punto es uno de los más importantes, debido a que se agregará la lógica que permite la inyección de dependencias.
En primer lugar, en HelperBundle
crea un archivo llamado DistantObject+SwiftTestHost.swift
con el siguiente contenido:
Al detenerse a observar este archivo, tenemos lo siguiente:
- Se crea una extensión de la clase
GreyHostApplicationDistantObject
, uno de los dos “objetos distantes” que posee EalrGrey y que utilizan la bibliotecaeDistantObject
para comunicarse con la aplicación principal. - La extensión implementa el protocolo
SwiftTestHost
, el cual define los métodos con los parámetros necesarios para realizar la inyección. - Como este bundle tiene como dependencia la aplicación principal, se puede hacer uso de la sentencia
@testable import ItunesSimpleSearch
y, por ende, se exponen las clases del módulo como si se estuviese escribiendo una prueba. Esto permite poder tener acceso la claseResolver
, la cual posee todas las dependencias del proyecto. UsandoResolver.shared.add
es posible reemplazar las dependencias de la aplicación.
Con todo esto listo, ya es posible crear una nueva prueba que realice la inyección.
La prueba final
Para demostrar que la inyección de dependencias es realizada correctamente, se creará la siguiente prueba:
- Seleccionar la barra de búsqueda y buscar
Metallica
utilizando internet. - Seleccionar el primer elemento de la lista de resultados.
- Verificar que los elementos presentados en la pantalla de detalle correspondan a una canción de
Romeo Santos
(sí, bastante diferente).
Para comenzar, crea un archivo de prueba de UI llamado IntegrationTests.swift
y agrega el siguiente contenido:
A partir de esto, podemos destacar lo siguiente:
- Se crea una extensión que contiene un objeto de tipo
SwiftTestHost
, el cual posee la referencia al “objeto distante” que está conectado con la aplicación principal. - En la prueba, se hace la inyección de dependencias llamando a:
self.host.injectLookupDependency(fromJSON: .lookup)
- Esa llamada produce que se ejecute el código presente en el archivo
DistantObject+SwiftTestHost.swift
, lo que a su vez hace que se reemplace la dependencia en la aplicación principal. - Además, junto con la verificación de la propiedad
accessibilityIdentifier
también se verifica el valoracessibilityLabel
para así estar seguros que el contenido mostrado corresponde al mock que fue inyectado.
Si ejecutamos la prueba, podemos ver el siguiente resultado:
A partir de acá, puedes crear otra prueba usando el contenido de lookup-2.json
o crear una prueba que verifique que se muestre en pantalla el mensaje de error. Las posibilidades son infinitas.
Conclusiones
Después de un largo camino recorrido, de unas cuantas pruebas escritas y por sobre todo después de muchas configuraciones aplicadas, puedo concluir lo siguiente:
- Integrar EarlGrey2 en un proyecto no es trivial. Si deseas crear pruebas de caja negra puedes agregar el
pod
y ya está. Sin embargo, para tener soporte de pruebas de caja blanca los pasos que hay que seguir son muchos y esto podría resultarle tedioso a más de alguno. Se requiere de mucha paciencia y dedicación (piensen que me tomó TRES artículos para explicar bien como integrarlo) - A pesar de lo anterior, si lo que deseas es tener una suite de pruebas de UI robusta y que pueda simular lo más posible la interacción del usuario y sin modificar la aplicación, todo ese esfuerzo lo vale. EarlGrey2 es una gran herramienta que lleva las pruebas de UI al siguiente nivel porque además de su gama de matcher que ya es buena, con su integración con
eDistantObject
agrega la posibilidad de modificar una aplicación ejecutándose y así poder probar distintas casuísticas. En el caso del proyecto de ejemplo, se produce un beneficio colateral que implica no necesitar crear servidores con mocks utilizando otras herramientas, ya que todo ese código está contenido en el bundle, haciendo toda esa lógica aún más mantenible y modificable. - Todo lo anterior solamente será posible si tu aplicación fue diseñada y construida utilizando patrones de diseño y que éstos estén bien aplicados. Es fundamental que al momento de construir tu aplicación lo hagas inmediatamente pensando en que debe ser capaz de ser probada, tanto a nivel unitario como a nivel funcional.
Hemos llegado al final de esta serie de artículos. Espero que te haya servido para conocer y querer utilizar esta gran herramienta. Si tienes alguna pregunta que deses hacer, no dudes en hacerla en la caja de comentarios.
El resultado final de toda esta integración, más algún contenido adicional se encuentran en la rama white-box-tests
del repositorio con el proyecto de prueba.
Muchas gracias por leer.