Hola Habr!
Mi nombre es Vitaliy Kotov, trabajo para Badoo y la mayoría de las veces trato con problemas de automatización de pruebas. Quiero compartir la solución a una de esas preguntas en este artículo.
Se tratará de cómo organizamos el proceso de trabajar las pruebas de IU con las pruebas A / B, de las cuales tenemos mucho. Hablaré sobre qué problemas encontramos y qué inundación encontramos al final. ¡Bienvenido a cat!

Hasta que empezamos ...
La palabra prueba es muy común en este artículo. Eso es porque estamos hablando de pruebas de IU y pruebas A / B al mismo tiempo. Siempre intenté separar estos dos conceptos y formular pensamientos para que el texto fuera fácil de leer. Si en algún lugar, sin embargo, me perdí la primera parte de la palabra y escribí simplemente "prueba", me refería a la prueba de IU.
Que tengas una buena lectura!
¿Qué son las pruebas A / B?
Entonces, antes que nada, definamos el concepto de prueba A / B. Aquí hay una cita de Wikipedia:
“Las pruebas A / B (pruebas A / B en inglés, pruebas divididas) son un método de investigación de mercado, cuya esencia es que el grupo de control de elementos se compara con un conjunto de grupos de prueba en los que se han cambiado uno o más indicadores, para para averiguar cuál de los cambios mejora el objetivo " Enlace .
En términos de nuestro proyecto, la presencia de una prueba A / B implica que alguna funcionalidad es diferente para diferentes usuarios. Destacaría varias opciones:
- La función está disponible para un grupo de usuarios, pero no está disponible para otro;
- La función está disponible para todos los usuarios, pero funciona de diferentes maneras;
- La función está disponible para todos los usuarios, funciona igual, pero se ve diferente;
- cualquier combinación de las tres opciones anteriores.
Para que toda esta lógica funcione, tenemos una herramienta en nuestra empresa llamada
UserSplit Tool , y nuestro desarrollador Rinat Akhmadeev habló sobre ello en detalle en este
artículo .
Hablaremos ahora sobre lo que significa tener pruebas A / B para el departamento de pruebas y para la automatización en particular.
Cobertura de prueba de IU
Cuando hablamos de la cobertura de UI, no estamos hablando de la cantidad de líneas de código que probamos. Esto es comprensible, porque incluso solo abrir una página puede involucrar muchos componentes, mientras que todavía no hemos probado nada.
A lo largo de los años de trabajo en el campo de la automatización de pruebas, he visto muchas formas de medir la cobertura de las pruebas de IU. No los enumeraré todos, solo diré que preferimos evaluar este indicador por la cantidad de funciones que cubren las pruebas de IU. Esta no es una forma ideal (personalmente no sé la forma ideal), pero en nuestro caso funciona.
Y aquí volvemos directamente al tema del artículo. ¿Cómo medir y mantener un buen nivel de cobertura de las pruebas de IU, cuando cada característica puede comportarse de manera diferente dependiendo del usuario que la use?
Cómo las funciones fueron cubiertas inicialmente por las pruebas de IU
Incluso antes de que apareciera la herramienta UserSplit Tool en la empresa y había realmente muchas pruebas A / B, nos adherimos a la siguiente estrategia para cubrir características con pruebas de IU: cubrir solo aquellas características que habían estado en producción durante algún tiempo y se habían establecido.
Y todo porque antes, cuando la función solo entraba en producción, todavía se "ajustaba" por un tiempo: su comportamiento y apariencia podían cambiar. Y tampoco pudo demostrar su valía y desapareció rápidamente de los ojos de los usuarios. Escribir pruebas de IU para características inestables es costoso y no se ha practicado con nosotros.
Con la introducción de las pruebas A / B en el proceso de desarrollo, nada ha cambiado al principio. Cada prueba A / B tenía un llamado "grupo de control", es decir, un grupo que vio un comportamiento predeterminado de la función. Fue sobre él que se escribieron las pruebas de IU. Todo lo que tenía que hacer al escribir pruebas de IU para tal característica era recordar habilitar al usuario con el comportamiento predeterminado. Llamamos a este proceso la fuerza del grupo A / B (de la fuerza inglesa).
Me
detendré en la descripción de la
fuerza con más detalle, ya que todavía jugará un papel en mi historia.
Fuerza para pruebas A / B y QaAPI
Hemos hablado repetidamente sobre QaAPI en nuestros artículos e informes. Sin embargo, por extraño que parezca, hasta ahora no hemos escrito un artículo completo sobre esta herramienta. Probablemente algún día este vacío se llenará. Mientras tanto, puede ver un video del discurso de mi colega Dmitry Marushchenko: "
4. El concepto de QaAPI: una mirada a las pruebas desde el otro lado de las barricadas ".
En pocas palabras, QaAPI le permite realizar solicitudes desde la prueba al servidor de aplicaciones a través de una puerta trasera especial para manipular cualquier dato. Con esta herramienta, por ejemplo, preparamos a los usuarios para casos de prueba específicos, les enviamos mensajes, cargamos fotos, etc.
Usando el mismo QaAPI, podemos forzar al grupo de prueba A / B; es suficiente para indicar el nombre de la prueba y el nombre del grupo deseado. La llamada de prueba se ve así:
QaApi::forceSpliTest(“Test name”, “Test group name”, {USER_ID or DEVICE_ID});
El último parámetro que especificamos es user_id o device_id, para lo cual esta fuerza debería comenzar a funcionar. Especificamos el parámetro device_id en el caso de un usuario no autorizado, ya que el parámetro user_id aún no está allí. Así es, para páginas no autorizadas también tenemos pruebas A / B.
Después de llamar a este método QaAPI, se garantiza que un usuario autorizado o el propietario del dispositivo vea la versión de la función que hemos falsificado. Estos son los desafíos que escribimos en las pruebas de IU, que cubrieron características que están bajo pruebas A / B.
Y así vivimos durante mucho tiempo. Las pruebas de IU solo cubrieron grupos de control de pruebas A / B. Entonces no había muchos, y funcionó. Pero el tiempo pasó; el número de pruebas A / B comenzó a aumentar, y casi todas las nuevas características comenzaron a ejecutarse bajo pruebas A / B. El enfoque de cubrir solo las versiones de control de características dejó de satisfacernos. Y aquí está el por qué ...
¿Por qué cubrir las pruebas A / B?
Problema uno: coberturaComo escribí anteriormente, con el tiempo, casi todas las nuevas características comenzaron a aparecer bajo pruebas A / B. Además del control, cada función tiene una más, dos o tres opciones más. Resulta que para tal característica, la cobertura en el mejor de los casos no excederá el 50%, y en el peor será de aproximadamente el 25%. Anteriormente, cuando había pocas características de este tipo, esto no tenía un efecto significativo en la tasa de cobertura total. Ahora, comenzó a renderizarse.
Problema dos: pruebas A / B largasAlgunas pruebas A / B ahora llevan bastante tiempo. Y continuamos siendo liberados dos veces al día (esto se puede encontrar en el artículo de nuestro ingeniero de control de calidad Ilya Kudinov "
Cómo hemos sobrevivido durante dos
años bajo las condiciones de dos liberaciones al día ").
Por lo tanto, la probabilidad de romper alguna versión de la prueba A / B durante este tiempo es increíblemente alta. Y esto ciertamente afectará la experiencia del usuario y negará todo el punto de la prueba A / B de la función: después de todo, una función puede mostrar malos resultados en alguna versión, no porque a los usuarios no les guste, sino porque no funciona como se esperaba.
Si queremos estar seguros del resultado de las pruebas A / B, no debemos permitir que ninguna versión de la función funcione de manera diferente a la esperada.
El tercer problema es la relevancia de las pruebas de IUExiste el lanzamiento de una prueba A / B. Esto significa que la prueba A / B ha recopilado suficientes estadísticas y el gerente de producto está listo para abrir la opción ganadora para todos los usuarios. El lanzamiento de la prueba A / B ocurre de forma asíncrona con el lanzamiento del código, ya que depende de la configuración de la configuración y no del código.
Suponga que la variante sin control ganó y mejoró. ¿Qué pasará con las pruebas de IU que solo lo cubrieron? Así es: se romperán. Pero, ¿qué pasa si se rompen una hora antes del lanzamiento de la compilación? ¿Podemos realizar pruebas de regresión de esta compilación? No Como sabes, con las pruebas rotas no llegarás lejos.
Por lo tanto, debe estar preparado para cerrar cualquier prueba A / B de antemano para que no interfiera con el rendimiento de las pruebas de IU y, como resultado, la próxima versión de la compilación.
ConclusiónLa conclusión de lo anterior es obvia: necesitamos cubrir las pruebas A / B con pruebas de IU en su totalidad, todas las opciones. ¿Es lógico? Si! Gracias a todos, divergir!
... broma! No es tan simple
Interfaz para pruebas A / B
Lo primero que pareció inconveniente fue el control sobre qué pruebas y características A / B ya estaban cubiertas, y cuáles aún no. Históricamente, llamamos pruebas de IU de acuerdo con el siguiente principio:
- nombre de la característica o página;
- descripción del caso;
- Prueba
Por ejemplo, ChatBlockedUserTest, RegistrationViaFacebookTest, etc. Empujar aquí también el nombre de la prueba dividida parecía incómodo. Primero, los nombres se volverían increíblemente largos. En segundo lugar, las pruebas tendrían que cambiarse de nombre al final de la prueba A / B, y esto tendría un efecto negativo en la recopilación de estadísticas que tengan en cuenta el nombre de la prueba de IU.
Agarrar el código para llamar al método QaAPI todo el tiempo sigue siendo un placer.
Así que decidimos eliminar todas las llamadas a QaApi :: forceSplitTest () del código de las pruebas de IU y transferir los datos sobre dónde se necesitan las fuerzas a la tabla MySQL. Para ella, hicimos una presentación de UI sobre Selenium Manager (hablé de eso
aquí ).
Se parece a esto:

En la tabla puede indicar para qué prueba de IU la fuerza de qué prueba A / B y en qué grupo queremos aplicar. Puede especificar el nombre de la prueba de interfaz de usuario, clase de prueba o Todo.
Además, podemos indicar si esta fuerza se aplica a usuarios autorizados o no autorizados.
A continuación, enseñamos las pruebas de IU al inicio para obtener datos de esta tabla y forzar aquellos que están directamente relacionados con la prueba en ejecución o con todas (todas) las pruebas.
Por lo tanto, logramos recolectar todas las manipulaciones de las pruebas A / B en un solo lugar. Ahora la lista de pruebas A / B cubiertas es fácil de ver.
Allí creamos un formulario para agregar nuevas pruebas A / B:

Todo esto le permite agregar y eliminar fácil y rápidamente la fuerza necesaria sin crear un compromiso, esperando que se descomponga en todas las nubes donde se ejecutan las pruebas de IU, etc.
Arquitectura de prueba de IU
La segunda cosa a la que decidimos prestar atención es una revisión del enfoque para escribir pruebas de IU para pruebas A / B.
En pocas palabras, te diré cómo escribimos pruebas de IU regulares. La arquitectura es bastante simple y familiar:
- clases de prueba: describe la lógica de negocios de la característica cubierta (de hecho, estos son los scripts de nuestras pruebas: hice esto, vi esto);
- Clases de PageObject: todas las interacciones con la interfaz de usuario y los localizadores se describen allí;
- Clases de TestCase: existen métodos comunes que no se relacionan directamente con la interfaz de usuario, pero que pueden ser útiles en varias clases de prueba (por ejemplo, interacción con QaAPI);
- clases principales: existe la lógica de aumentar la sesión, el registro y otras cosas que no necesita tocar al escribir una prueba regular.
En general, esta arquitectura nos satisface completamente. Sabemos que si la interfaz de usuario ha cambiado, solo las clases de PageObject deben cambiarse (mientras que las pruebas en sí no deberían verse afectadas). Si la lógica de negocios de una característica ha cambiado, cambiamos el escenario.
Como escribí en un
artículo anterior , todos trabajan con pruebas de IU: tanto los chicos del departamento de pruebas manuales como los desarrolladores. Cuanto más simple y comprensible sea este proceso, más a menudo las personas que no están directamente relacionadas con ellos realizarán pruebas.
Pero, como escribí anteriormente, a diferencia de las características bien establecidas, las pruebas A / B van o vienen. Si los escribimos en el mismo formato que las pruebas de IU normales, tendremos que eliminar permanentemente el código de muchos lugares diferentes después de completar las pruebas A / B. Usted entiende, para refactorizar, especialmente cuando todo funciona sin él, no siempre es posible asignar tiempo.
Sin embargo, no queremos dejar que nuestras clases crezcan demasiado con métodos y localizadores no utilizados, esto hará que los mismos PageObjects sean difíciles de usar. ¿Cómo hacer tu vida más fácil?
Luego PhpStorm vino a nuestro rescate (gracias a los chicos de JetBrains por el IDE conveniente), es decir, esta
característica .
En resumen, permite usar etiquetas especiales para dividir el código en las llamadas regiones. Lo intentamos y nos gustó. Comenzamos a escribir pruebas de IU temporales para pruebas A / B activas en un archivo, dividiendo las zonas de código en regiones que indican la clase en la que este código debe colocarse en el futuro.
Como resultado, el código de prueba se parecía a esto:

En cada región hay un código que pertenece a una clase particular. Seguramente en otros IDEs hay algo similar.
Por lo tanto, cubrimos todas las variantes de la prueba A / B con una clase de prueba, colocando los métodos y localizadores de PageObject allí. Y después de su finalización, primero eliminamos las opciones perdedoras de la clase, y luego distribuimos con bastante facilidad el código restante en las clases deseadas de acuerdo con lo que se indica en la región.
¿Cómo cerramos ahora las pruebas A / B?
No puede simplemente tomar y cubrir todas las pruebas A / B con pruebas de IU a la vez. Por otro lado, no existe tal tarea. El desafío en términos de automatización es cubrir rápidamente solo pruebas importantes y de larga duración.
Sin embargo, antes del lanzamiento de cualquier, incluso la prueba A / B más pequeña, quiero poder ejecutar todas las pruebas de IU en la versión ganadora y asegurarme de que todo funcione como debería y replicamos la funcionalidad de trabajo de alta calidad para el 100% de los usuarios.
La solución mencionada anteriormente con una tabla MySQL no es adecuada para este propósito. El hecho es que si agrega fuerza allí, inmediatamente comenzará a encenderse para todas las pruebas de IU. Además de la preparación (nuestro entorno de preproducción, donde ejecutamos un conjunto completo de pruebas), esto también afectará las pruebas de IU lanzadas contra ramas de tareas individuales. Los colegas del departamento de pruebas manuales trabajarán con los resultados de esos lanzamientos. Y si una prueba A / B fallida tiene un error, las pruebas para sus tareas también caerán y los chicos pueden decidir que el problema está en su tarea, y no en la prueba A / B. Debido a esto, las pruebas y las pruebas pueden llevar mucho tiempo (nadie estará satisfecho).
Lo hemos logrado con cambios mínimos hasta ahora, agregando la capacidad de especificar el entorno de destino a la tabla:

Este entorno se puede cambiar sobre la marcha en un registro existente. Por lo tanto, podemos agregar fuerza solo para la puesta en escena, sin afectar los resultados de pasar las pruebas en tareas individuales.
Para resumir
Entonces, antes del comienzo de esta historia, nuestras pruebas de IU cubrían solo los grupos principales (control) de pruebas A / B. Pero nos dimos cuenta de que queremos más y llegamos a la conclusión de que también es necesario cubrir otros casos de prueba A / B.
En resumen:
- creamos una interfaz para un control conveniente sobre la cobertura de las pruebas A / B; Como resultado, ahora tenemos toda la información sobre el funcionamiento de las pruebas de IU con pruebas A / B;
- Hemos desarrollado para nosotros una forma de escribir pruebas de IU temporales con un flujo simple y efectivo para su posterior eliminación o transferencia a las filas del permanente;
- aprendimos cómo probar fácilmente y sin problemas las versiones de pruebas A / B, sin interferir con otras pruebas de IU en ejecución y sin compromisos innecesarios en Git.
Todo esto hizo posible adaptar la automatización de pruebas a las características que cambian constantemente, para controlar fácilmente y aumentar el nivel de cobertura, y no crecer demasiado con un código heredado.
¿Tiene alguna experiencia en traer a primera vista una situación caótica a un orden controlado y simplificar su vida para usted y sus colegas? Compártelo en los comentarios. :)
Gracias por su atencion! Y feliz año nuevo!