Tres años de autotest: cómo aumentar la velocidad y no solo


Hola, soy Alexey, desarrollador de pila completa de la plataforma Vimbox. Cuando llegué a Skyeng, decidieron aquí si valía la pena dedicar tiempo al sistema de autotest y me pidieron que compartiera mi experiencia del trabajo anterior. Y tuve esa experiencia: cuando salimos del lugar anterior, escribimos en php y torcimos más de 3 mil pruebas. Como resultado, hice una pequeña presentación interna sobre el rastrillo que logré pisar en unos años desarrollando estas pruebas automáticas, luchando por su velocidad, legibilidad del código y eficiencia general. La presentación parecía útil para los colegas, así que la puse en el texto para que también sea útil para un público más amplio.


Para empezar, los términos que se discutirán en el artículo:


  • Prueba de aceptación: prueba de extremo a extremo: aquí el navegador o el emulador del navegador ejecuta el script
  • Prueba unitaria ( prueba unitaria) - prueba de método
  • Prueba funcional : una prueba de un controlador o componente, cuando se trata de frontend
  • Fixture : el estado del entorno de prueba necesario para que la prueba funcione (variables globales, datos en la base de datos y otros participantes en el script de prueba)

Pros y contras de diferentes tipos de pruebas



Pruebas de aceptación


  • Pros: obvio por el nombre, tales pruebas cubren todo el sistema de arriba a abajo, asegúrese de que todo funcione como debería.
  • Contras: los comentarios de estas pruebas son muy lentos, funcionan durante mucho tiempo, no son muy confiables, hay muchos falsos positivos. En un trabajo anterior, también nos enfrentamos con el hecho de que los controladores web no detectaron algunos de los elementos que vimos con nuestros ojos. Ahora esto probablemente se ha solucionado, pero luego tuve que abandonarlos.

Pruebas unitarias


  • Pros: fácil de escribir, trabaja rápidamente. Cubren un pequeño fragmento de código, no necesita muchos estados, por lo tanto, tampoco necesita un dispositivo grande.
  • Contras: inestable a los cambios en la arquitectura o estructura interna del código. Si necesita fusionar dos métodos en uno o por separado, seleccione una clase, elimine un método, debe volver a escribir las pruebas.

Las pruebas funcionales son una solución intermedia.


  • Pros: aceptación más confiable, más resistente a los cambios en la estructura del código que modular.
  • Contras: más lento que modular, más difícil de escribir, porque Necesito preparar un gran accesorio.

La lucha por la velocidad


En el antiguo trabajo, escribimos muchas pruebas funcionales, y el principal desafío era la velocidad de respuesta. Tuve que esperar mucho tiempo para obtener el resultado, incluso con un lanzamiento local en la computadora del desarrollador. La velocidad era tan lenta que no fue posible aplicar el enfoque de "desarrollo a través de pruebas", ya que implica ejecutar pruebas automáticas varias veces por hora. Encontró un cuello de botella: trabajar con la base de datos. ¿Cómo lidiar con eso?


Experiencia primero: moki


Mock in PhpUnit es un objeto creado dinámicamente cuya clase se hereda dinámicamente de la clase parodiada. Puede configurar lo que devolverán los métodos del mok, puede verificar qué métodos del moq cuántas veces con qué parámetros se llamaron


La principal ventaja de moki: le permiten cortar piezas enteras de funcionalidad. Reemplazando el servicio con moch, nos deshacemos de la necesidad de pensar qué está sucediendo allí, desarrollar scripts y accesorios adicionales para que todo funcione correctamente. Como resultado: menos accesorios, y la velocidad de respuesta es mayor debido al hecho de que cortamos el código adicional que ejecuta las consultas a la base de datos.


La ventaja implícita de las turbas es que hacen que sea mejor organizar las adicciones. Cuando escribe un código, sabiendo que será necesario escribir una prueba en él, donde algo es reemplazado por mokami, inmediatamente piensa en las dependencias.


Menos : el código de prueba está demasiado adjunto a la implementación. Durante la prueba, debemos crear un objeto simulado y pensar en qué métodos deberían llamarse.


La segunda desventaja encontrada es que las pruebas se han vuelto menos confiables. Ellos "no notan" incluso los cambios en la interfaz, sin mencionar la implementación. Es decir eliminamos el método en algún lugar y después de mucho tiempo descubrimos que las pruebas que lo cubren todavía funcionan como si nada hubiera pasado, porque vimos su simulación, y él fingió que todo estaba bien.


Considero que la experiencia con mokas no tuvo éxito en términos de acelerar las pruebas.


Experiencia dos: SQLite


La siguiente opción es SQLite DBMS , puede crear una base de datos en RAM. Tuve que escribir un esquema de traductor PostgreSQL en SQLite, después de cada migración se generó un nuevo esquema SQLite. Las pruebas de este circuito crearon una base de datos vacía en la RAM. Este enfoque aumentó la velocidad de las pruebas en máquinas locales de dos a cuatro veces. Se volvió realista ejecutar todo el conjunto de pruebas varias veces por hora.


Pero había contras. Hemos perdido muchas de las características nativas de PostgreSQL (json, algunas funciones agregadas útiles y más). Las consultas tenían que escribirse para que funcionaran tanto en PostgreSQL como en SQLite.


Experiencia tres: optimización de PostgreSQL


Esta decisión estaba funcionando, pero causó algo de dolor. En algún momento, aprendimos que PostgreSQL puede optimizarse para las pruebas automáticas, lo que reduce el tiempo de respuesta unas cuatro veces. Para hacer esto, agregue algunas configuraciones a postgresql.conf:


fsync=off synchronous_commit=off full_page_writes=off 

Estas son configuraciones de confiabilidad, garantizan que si el servidor muere en medio de una transacción, terminará correctamente cuando todo comience a funcionar nuevamente. Está claro que tales configuraciones no se pueden realizar en la producción, pero era conveniente en las pruebas automáticas.


Esta configuración se aplica a todo el clúster, afecta a todas las bases de datos, no se puede aplicar a ninguna base de datos. Si logra localizar las bases de datos en un clúster separado y deshabilita fsync en él, esto es muy conveniente.


Un poco sobre new


También me gustaría mencionar el peligro del new operador. Los servicios creados con su ayuda no pueden ser reemplazados por mokas y stubs. Conclusión


  • No use new para crear objetos que son esencialmente servicios.
  • Se puede usar en fábricas, porque se pueden reemplazar. Pero las fábricas mismas no deberían crearse a través de new .
  • Se puede usar para crear modelos, entidades, DTO (objeto de transferencia de datos), objetos de valor.

Conclusiones de tres años de experiencia.


  • En el trabajo anterior, rechazamos las pruebas de aceptación, pero ahora las volvería a intentar: lo más probable es que se hayan corregido muchos errores en los controladores web.
  • Si necesita cubrir una nueva funcionalidad con pruebas, debe escribir solo pruebas funcionales de controladores / componentes. En esta situación, tenemos un alto riesgo de cambios estructurales, las pruebas unitarias son inestables para ellos.
  • No debería haber muchas pruebas de este tipo, porque muchas == lentamente, no funcionan tan rápido como las modulares. Vale la pena cubrir solo aquellos casos que pueden "disparar" (tienen la probabilidad de un error en el futuro).
  • Las pruebas unitarias se escriben en métodos ricos en algoritmos (lógica compleja que necesita ser probada) o en métodos con poco riesgo de cambios estructurales en el futuro.
  • Contras de moka generalmente exceden los pros. Tiene sentido usarlos solo como una sustitución de puertas de enlace en API externas y, a veces, servicios de código heredado, que son muy difíciles de probar.
  • Si decide escribir código sin una prueba, es aconsejable pensar, "cuando lo cree, ¿qué pasa si en el futuro todavía queremos escribir una prueba para él?"
  • Las pruebas deben ser fáciles y agradables de escribir, brindan confiabilidad, confianza, ayudan a comprender mejor el código y administran las dependencias.
  • Presta atención a la legibilidad de las pruebas. Uno debe relacionarse con el código de prueba de la misma manera que el código que cubre.
  • Accesorios DB: parte de la prueba, también debe ser legible

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


All Articles