C贸mo probamos Sberbank Online en iOS



En el art铆culo anterior, nos familiarizamos con la pir谩mide de pruebas y los beneficios de las pruebas automatizadas. Pero la teor铆a suele ser diferente de la pr谩ctica. Hoy queremos hablar sobre nuestra experiencia en probar el c贸digo de aplicaci贸n utilizado por millones de usuarios de iOS. Y tambi茅n sobre el dif铆cil camino que nuestro equipo tuvo que recorrer para lograr un c贸digo estable.

La situaci贸n es la siguiente: supongamos que los desarrolladores lograron convencerse a s铆 mismos y al negocio de la necesidad de cubrir la base del c贸digo con pruebas. Con el tiempo, el proyecto se ha convertido en m谩s de una docena de miles de unidades y m谩s de mil pruebas de IU. Una base de prueba tan grande dio lugar a varios problemas, cuya soluci贸n queremos contar.

En la primera parte del art铆culo nos familiarizaremos con las dificultades que surgen al trabajar con pruebas unitarias limpias (sin integraci贸n), en la segunda parte consideraremos las pruebas de IU. Para saber c贸mo estamos mejorando la estabilidad de las pruebas, bienvenido a Cat.

En un mundo ideal, con un c贸digo fuente sin cambios, las pruebas unitarias siempre deben mostrar el mismo resultado, independientemente del n煤mero y la secuencia de los inicios. Y las pruebas de ca铆da constante no deben pasar por la barrera del servidor de integraci贸n continua (CI).


En realidad, uno puede encontrar el hecho de que la misma prueba unitaria mostrar谩 un resultado positivo o negativo, lo que significa "parpadeo". La raz贸n de este comportamiento radica en la implementaci贸n deficiente del c贸digo de prueba. Adem谩s, dicha prueba puede pasar CI con una ejecuci贸n exitosa, y m谩s tarde comenzar谩 a recaer en la solicitud de extracci贸n (PR) de otras personas. En una situaci贸n similar, existe el deseo de deshabilitar esta prueba o jugar a la ruleta y ejecutar la ejecuci贸n de CI nuevamente. Sin embargo, este enfoque es antiproductivo, ya que socava la credibilidad de las pruebas y carga a CI con un trabajo sin sentido.

Este tema se destac贸 este a帽o en la conferencia internacional WWDC de Apple:

  • Esta sesi贸n habla sobre pruebas paralelas, an谩lisis de la cobertura de un c贸digo objetivo individual con pruebas y tambi茅n sobre el orden de lanzamiento de las pruebas.
  • Aqu铆 Apple habl贸 sobre probar solicitudes de red, pirater铆a, notificaciones de prueba y la velocidad de las pruebas.

Pruebas unitarias


Para combatir las pruebas de parpadeo, utilizamos la siguiente secuencia de acciones:

imagen

0. Evaluamos el c贸digo de prueba de calidad de acuerdo con criterios b谩sicos: aislamiento, correcci贸n de moka, etc. Seguimos la regla: con una prueba parpadeante, cambiamos el c贸digo de prueba y no el c贸digo de prueba.

Si este elemento no ayuda, proceda de la siguiente manera:

1. Arreglamos y reproducimos las condiciones bajo las cuales cae la prueba;
2. Encuentra la raz贸n por la ca铆da;
3. Cambie el c贸digo de prueba o el c贸digo de prueba;
4. Vaya al primer paso y verifique si se ha eliminado la causa de la ca铆da.

Jugar oto帽o


La opci贸n m谩s simple y obvia es ejecutar una prueba de problema en la misma versi贸n de iOS y en el mismo dispositivo. Como regla general, en este caso la prueba es exitosa y aparece la idea: "Todo funciona para m铆 localmente, reiniciar茅 el ensamblaje en CI". Eso es, de hecho, el problema no se ha resuelto, y la prueba contin煤a cayendo con otra persona.

Por lo tanto, en el siguiente paso de verificaci贸n, debe ejecutar localmente todas las pruebas unitarias de la aplicaci贸n para identificar el impacto potencial de una prueba en otra. Pero incluso despu茅s de dicha verificaci贸n, el resultado de su prueba puede ser positivo, pero el problema permanece sin ser detectado.

Si toda la secuencia de prueba fue exitosa y no se registr贸 la ca铆da esperada, puede repetir la ejecuci贸n un n煤mero significativo de veces.
Para hacer esto, en la l铆nea de comando, debe ejecutar un bucle con xcodebuild:

#! /bin/sh x=0 while [ $x -le 100 ]; do xcodebuild -configuration Debug -scheme "TargetScheme" -workspace App.wcworkspace -sdk iphonesimulator -destination "platfrom=iOS Simulator, OS=11.3, name=iPhone 7" test >> "report.txt"; x=$(( $x +1 )); done 

Como regla, esto es suficiente para reproducir la ca铆da y pasar al siguiente paso: identificar la causa de la ca铆da registrada.

Motivos de la ca铆da y posibles soluciones.


Considere las causas principales de las pruebas unitarias intermitentes que puede encontrar en su trabajo, las herramientas para identificarlas y las posibles soluciones.

Hay tres grupos principales de razones para la ca铆da de las pruebas:

Pobre aislamiento

Por aislamiento queremos decir un caso especial de encapsulaci贸n, a saber: un mecanismo de lenguaje que permite restringir el acceso de algunos componentes del programa a otros.

El aislamiento del entorno juega un papel importante, ya que para la pureza de la prueba, nada deber铆a afectar a las entidades evaluadas. Se debe prestar especial atenci贸n a las pruebas que tienen como objetivo verificar el c贸digo. Utilizan entidades de estado globales como variables globales, llavero, red, CoreData, Singleton, NSUserDefaults, etc. Es en estas 谩reas donde surge el mayor n煤mero de lugares potenciales para la manifestaci贸n de un aislamiento deficiente. Supongamos que, al crear un entorno de prueba, se establece un estado global, que se usa impl铆citamente en otro c贸digo de prueba. En este caso, la prueba que verifica el c贸digo bajo prueba puede comenzar a "parpadear", porque, dependiendo de la secuencia de pruebas, pueden surgir dos situaciones, cuando el estado global est谩 configurado y cuando no est谩 configurado. A menudo, las dependencias descritas son impl铆citas, por lo que puede olvidarse accidentalmente de configurar / restablecer dichos estados globales.

Para que las dependencias sean claramente visibles, puede usar el principio de Inyecci贸n de dependencias (DI), a saber: pasar la dependencia a trav茅s de los par谩metros del constructor o la propiedad del objeto. Esto facilitar谩 la sustituci贸n de dependencias simuladas en lugar de un objeto real.

Llamada asincron铆a

Todas las pruebas unitarias se realizan sincr贸nicamente. La dificultad de probar la asincron铆a surge porque la llamada del m茅todo de prueba en la prueba se "congela" en anticipaci贸n de la finalizaci贸n del alcance de la prueba unitaria. El resultado ser谩 una ca铆da estable en la prueba.

 //act [self.testService loadImageFromUrl:@"www.google.ru" handler:^(UIImage * _Nullable image, NSError * _Nullable error) { //assert OCMVerify([cacheMock imageAtPath:OCMOCK_ANY]); OCMVerify([cacheMock dateOfFileAtPath:OCMOCK_ANY]); OCMVerify([imageMock new]); [imageMock stopMocking]; }]; [self waitInterval:0.2]; 

Para probar dicha prueba, hay varios enfoques:

  1. Ejecute NSRunLoop
  2. waitForExpectationsWithTimeout

Ambas opciones requieren que especifique un argumento con un tiempo de espera. Sin embargo, no se puede garantizar que el intervalo seleccionado sea suficiente. A nivel local, su prueba pasar谩, pero en un CI con mucha carga puede que no haya suficiente energ铆a y se caer谩; desde aqu铆 aparecer谩 un "parpadeo".

Tengamos alg煤n tipo de servicio de procesamiento de datos. Queremos verificar que despu茅s de recibir una respuesta del servidor, transfiera estos datos para su posterior procesamiento.

Para enviar solicitudes a trav茅s de la red, el servicio utiliza el cliente para trabajar con 茅l.

Dicha prueba se puede escribir de forma asincr贸nica utilizando un servidor simulado para garantizar respuestas de red estables.

 @interface Service : NSObject @property (nonatomic, strong) id<APIClient> apiClient; @end @protocol APIClient <NSObject> - (void)getDataWithCompletion:(void (^)(id responseJSONData))completion; @end - (void)testRequestAsync { // arrange __auto_type service = [Service new]; service.apiClient = [APIClient new]; XCTestExpectation *expectation = [self expectationWithDescription:@"Request"]; // act id receivedData = nil; [self.service receiveDataWithCompletion:^(id responseJSONData) { receivedData = responseJSONData; [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) { expect(receivedData).notTo.beNil(); expect(error).to.beNil(); }]; } 

Pero la versi贸n s铆ncrona de la prueba ser谩 m谩s estable y le permitir谩 deshacerse de los tiempos de espera.

Para 茅l necesitamos un simulacro s铆ncrono APIClient

 @interface APIClientMock : NSObject <APIClient> @end @implementation - (void)getDataWithCompletion:(void (^)(id responseJSONData))completion { __auto_type fakeData = @{ @"key" : @"value" }; if (completion != nil) { completion(fakeData); } } @end 

Entonces la prueba se ver谩 m谩s simple y funcionar谩 m谩s estable

 - (void)testRequestSync { // arrange __auto_type service = [Service new]; service.apiClient = [APIClientMock new]; // act id receivedData = nil; [self.service receiveDataWithCompletion:^(id responseJSONData) { receivedData = responseJSONData; }]; expect(receivedData).notTo.beNil(); expect(error).to.beNil(); } 

La operaci贸n asincr贸nica se puede aislar encapsulando una entidad separada, que se puede probar de forma independiente. El resto de la l贸gica necesita ser probada sincr贸nicamente. Este enfoque evitar谩 la mayor铆a de los escollos provocados por la asincron铆a.

Como opci贸n, en el caso de actualizar la capa de interfaz de usuario desde el subproceso de fondo, puede verificar si estamos en el subproceso principal y qu茅 suceder谩 si realizamos una llamada desde la prueba:

 func performUIUpdate(using closure: @escaping () -> Void) { // If we are already on the main thread, execute the closure directly if Thread.isMainThread { closure() } else { DispatchQueue.main.async(execute: closure) } } 

Para una explicaci贸n detallada, vea el art铆culo de D. Sandell .

Prueba de c贸digo m谩s all谩 de su control
A menudo nos olvidamos de las siguientes cosas:

  • la implementaci贸n de los m茅todos puede depender de la localizaci贸n de la aplicaci贸n,
  • Hay m茅todos privados en el SDK que pueden ser llamados por las clases marco,
  • La implementaci贸n de los m茅todos puede depender de la versi贸n del SDK


Los casos anteriores introducen incertidumbre al escribir y ejecutar pruebas. Para evitar consecuencias negativas, debe ejecutar pruebas en todas las configuraciones regionales, as铆 como en las versiones de iOS compatibles con su aplicaci贸n. Por separado, debe tenerse en cuenta que no es necesario probar el c贸digo cuya implementaci贸n est谩 oculta para usted.

Con esto, queremos completar la primera parte del art铆culo sobre pruebas automatizadas de la aplicaci贸n Sberbank Online iOS, dedicada a las pruebas unitarias.

En la segunda parte del art铆culo, hablaremos sobre los problemas que ocurrieron al escribir 1500 pruebas de IU, as铆 como las recetas para superarlas.

El art铆culo fue escrito con regno : Anton Vlasov, jefe de desarrollo y desarrollador de iOS.

Source: https://habr.com/ru/post/es434320/


All Articles