¿Es una buena práctica ejecutar pruebas unitarias en los ganchos de control de versiones?

40

Desde el punto de vista técnico, es posible agregar algunos ganchos de empuje pre / post que ejecutarán pruebas unitarias antes de permitir la combinación de un compromiso específico a la rama predeterminada remota.

Mi pregunta es: ¿es mejor mantener las pruebas unitarias en el canal de compilación (por lo tanto, introducir confirmaciones rotas para el repo) o es mejor no permitir que ocurran confirmaciones "malas"?

Me doy cuenta de que no estoy limitado con estas dos opciones. Por ejemplo, puedo permitir que todas las confirmaciones se bifurquen y prueben antes de empujar la confirmación de combinación al repositorio. Pero si tiene que elegir exactamente entre estas dos soluciones, ¿cuál elegirá y por qué exactamente?

    
pregunta shabunc 24.10.2014 - 00:39

9 respuestas

33

No, no lo es, por dos razones:

Velocidad

Los compromisos deben ser rápidos. Un compromiso que toma 500 ms., Por ejemplo, es demasiado lento y alentará a los desarrolladores a cometer con más moderación. Dado que en cualquier proyecto más grande que Hello World, tendrá docenas o cientos de pruebas, tomará demasiado tiempo ejecutarlas durante la pre-confirmación.

Por supuesto, las cosas empeoran para proyectos más grandes con miles de pruebas que se ejecutan por minutos en una arquitectura distribuida, o semanas o meses en una sola máquina.

Lo peor es que no hay mucho que puedas hacer para hacerlo más rápido. Los proyectos Python pequeños que tienen, digamos, pruebas de cien unidades, toman al menos un segundo para ejecutarse en un servidor promedio, pero a menudo mucho más. Para una aplicación C #, tendrá un promedio de cuatro a cinco segundos, debido al tiempo de compilación.

Desde ese momento, puede pagar $ 10 000 extra por un mejor servidor que reducirá el tiempo, pero no mucho, o realizar pruebas en varios servidores, lo que solo ralentizará las cosas.

Ambos pagan bien cuando tiene miles de pruebas (así como pruebas funcionales, de sistema y de integración), lo que permite ejecutarlas en cuestión de minutos en lugar de semanas, pero esto no le ayudará en proyectos a pequeña escala. / p>

Lo que puedes hacer, en cambio, es:

  • Aliente a los desarrolladores a ejecutar pruebas fuertemente relacionadas con el código que modificaron localmente antes de realizar un compromiso. Posiblemente no pueden ejecutar miles de pruebas unitarias, pero pueden ejecutar de cinco a diez de ellas.

    Asegúrese de que encontrar pruebas relevantes y ejecutarlas sea realmente fácil (y rápido). Visual Studio, por ejemplo, puede detectar qué pruebas pueden verse afectadas por los cambios realizados desde la última ejecución. Otros IDE / plataformas / idiomas / marcos pueden tener una funcionalidad similar.

  • Mantener el compromiso lo más rápido posible. Hacer cumplir las reglas de estilo está bien, porque a menudo, es el único lugar para hacerlo, y porque tales controles son a menudo increíblemente rápidos. Hacer el análisis estático está bien tan pronto como se mantiene rápido, lo que rara vez es el caso. La ejecución de las pruebas unitarias no está bien.

  • Ejecute pruebas unitarias en su servidor de integración continua.

  • Asegúrese de que los desarrolladores estén informados automáticamente cuando rompieron la compilación (o cuando las pruebas unitarias fallaron, que es prácticamente lo mismo si considera a un compilador como una herramienta que revisa algunos de los posibles errores que puede introducir en su código).

    Por ejemplo, ir a una página web para comprobar las últimas compilaciones no es una solución. Deben ser informados automáticamente . Mostrar una ventana emergente o enviar un SMS son dos ejemplos de cómo pueden ser informados.

  • Asegúrese de que los desarrolladores entiendan que no es correcto romper la compilación (o que fallan las pruebas de regresión), y que tan pronto como suceda, su principal prioridad es solucionarlo. No importa si están trabajando en una función de alta prioridad que su jefe le pidió que enviara para mañana: fallaron la compilación, deberían arreglarla.

Seguridad

El servidor que aloja el repositorio no debe ejecutar código personalizado, como pruebas unitarias, especialmente por razones de seguridad. Esos motivos ya se explicaron en ¿Corredor de CI en el mismo servidor de GitLab?

Si, por otra parte, su idea es iniciar un proceso en el servidor de compilación desde el enganche de pre-confirmación, entonces ralentizará aún más las confirmaciones.

    
respondido por el Arseni Mourzenko 24.10.2014 - 02:05
39

Déjame ser el que esté en desacuerdo con mis colegas que respondieron.

Esto se conoce como check-ins cerrados en el mundo TFS, y espero que en otro lugar. Cuando intenta realizar el check-in en una sucursal con el check-in cerrado, el shelveset se envía al servidor, lo que garantiza que se realicen los cambios y que pasen las pruebas unitarias especificadas (leer: todas). Si no lo hacen, te notifica que eres un mono malo que rompió la construcción. Si lo hacen, los cambios entran en el control de código fuente (¡yay!).

En mi experiencia, los controles cerrados son uno de los procesos más importantes para realizar pruebas de unidad con éxito, y por extensión, la calidad del software.

¿Por qué?

  • Debido a que los check-ins cerrados obligan a las personas a corregir pruebas rotas. Tan pronto como los exámenes rotos se convierten en algo que la gente puede hacer en lugar de debe hacer, se desactiva la prioridad de los ingenieros perezosos y / o de los empresarios agresivos.
    • Cuanto más se rompe una prueba, más difícil (y costosa) es corregirla.
  • Debido a que tan pronto como la gente debería realizar las pruebas en lugar de debe realizar las pruebas, las pruebas son eludidas por ingenieros perezosos / olvidados y / o por empresarios agresivos.
  • Porque tan pronto como las pruebas de unidad afectan su tiempo de compromiso, las personas realmente comienzan a preocuparse por hacer sus pruebas de unidad . La velocidad importa. La reproducibilidad importa. La fiabilidad importa. El aislamiento importa.

Y, por supuesto, está el beneficio que mencionó originalmente: cuando tiene registros de entrada cerrados y un conjunto sólido de pruebas, cada conjunto de cambios es "estable". Se ahorra toda la sobrecarga (y el potencial de error) de "¿Cuándo fue la última versión correcta?" - todas las construcciones son lo suficientemente buenas como para desarrollar en contra.

Sí, lleva tiempo construir y ejecutar las pruebas. En mi experiencia, 5-10 minutos para una aplicación de C # de buen tamaño y ~ 5k pruebas de unidad. Y eso no me importa. Sí, la gente debe registrarse con frecuencia. Pero también deben actualizar sus tareas con frecuencia, o revisar su correo electrónico, o tomar un café o docenas de otras cosas que "no funcionan en el código" que conforman el trabajo de un ingeniero de software para ocupar ese tiempo. Revisar el código incorrecto es mucho más costoso que entre 5 y 10 minutos.

    
respondido por el Telastyn 24.10.2014 - 21:19
39

Los compromisos deben ejecutarse rápido. Cuando confirmo algún código, quiero que sea enviado al servidor. No quiero esperar unos minutos mientras se ejecuta una batería de pruebas. Soy responsable de lo que envío al servidor y no necesito que nadie me cuide con los ganchos de confirmación.

Dicho esto, una vez que llega al servidor, debe analizarse, probarse en la unidad y construirse inmediatamente (o dentro de un período de tiempo corto). Esto me alertaría sobre el hecho de que las pruebas unitarias se rompen, o no se compilaron, o hice un lío con las herramientas de análisis estático disponibles. Cuanto más rápido se haga esto (la compilación y el análisis), más rápido será mi retroalimentación y más rápido podré solucionarlo (los pensamientos no se han intercambiado completamente de mi cerebro).

Así que no, no pongas pruebas y demás en los ganchos de confirmación en el cliente. Si tiene que hacerlo, colóquelos en el servidor en una confirmación posterior (porque no tiene un servidor de CI) o en el servidor de compilación de CI y adviértame adecuadamente si hay problemas con el código. Pero, en primer lugar, no impida que ocurra el compromiso.

También debo señalar que con algunas interpretaciones de Test Driven Development, un debería verificar en una prueba unitaria que rompe primero . Esto demuestra y documenta que el error está presente. Luego, un registro posterior sería el código que repara la prueba de la unidad. La prevención de cualquier registro hasta que se aprueben las pruebas unitarias reduciría el valor efectivo de registrar una prueba unitaria que no documente el problema.

Relacionado: ¿Debo realizarme pruebas unitarias para detectar defectos conocidos? y ¿Cuál es el valor de verificar las pruebas unitarias fallidas?

    
respondido por el user40980 24.10.2014 - 02:11
10

En principio, creo que tiene sentido evitar que las personas realicen cambios en la línea principal que rompe la construcción. Es decir, el proceso para realizar cambios en la rama principal de su repositorio debe requerir garantizar que todas las pruebas aún se aprueben. Romper la construcción es simplemente demasiado costoso en términos de tiempo perdido para que todos los ingenieros en el proyecto hagan cualquier otra cosa.

Sin embargo, la solución particular de los enlaces de confirmación no es un buen plan.

  1. El desarrollador tiene que esperar a que se ejecuten las pruebas mientras se confirma. Si el desarrollador tiene que esperar en su estación de trabajo para que pasen todas las pruebas, ha perdido un valioso tiempo de ingeniero. El ingeniero debe poder pasar a la siguiente tarea, incluso si tendrá que volver a cambiar porque las pruebas fallaron.
  2. Los desarrolladores pueden querer cometer código roto en una rama. En una tarea más grande, la versión del código para desarrolladores puede pasar mucho tiempo sin pasar. Obviamente, fusionar ese código en la línea principal sería muy malo. Pero es bastante importante que el desarrollador aún pueda usar el control de versiones para seguir su progreso.
  3. Hay motivos ocasionales para omitir el proceso y omitir las pruebas.
respondido por el Winston Ewert 24.10.2014 - 05:07
3

No, no deberías, como han señalado otras respuestas.

Si desea tener una base de código que se garantiza que no tendrá pruebas fallidas, en lugar de eso, podría desarrollarse en las ramas de características y hacer solicitudes en el maestro. Luego puede definir las condiciones previas para aceptar esas solicitudes de extracción. El beneficio es que puede empujar realmente rápido y las pruebas se ejecutan en segundo plano.

    
respondido por el Yogu 24.10.2014 - 08:10
2

Tener que esperar para una compilación y pruebas exitosas en cada compromiso con la rama principal es realmente horrible, creo que todos están de acuerdo con esto.

Pero hay otras formas de lograr una rama principal consistente. Aquí hay una sugerencia, un poco similar en la vena de check-ins cerrados en TFS, pero que se pueden generalizar a cualquier sistema de control de versiones con sucursales, aunque en su mayoría usaré los términos git:

  • Tenga una rama de prueba en la que solo pueda realizar combinaciones entre sus ramas de desarrollo y la rama principal

  • Configure un gancho que inicie o ponga en cola una compilación y pruebe las confirmaciones realizadas en la rama de preparación, pero eso no hace que el interlocutor espere

  • En compilación y pruebas exitosas, automáticamente haga que la rama principal avance hacia adelante si está actualizada

    Nota: no automáticamente fusionar en la rama principal, ya que la combinación probada, si no es una combinación desde el punto de vista de la rama principal, puede fallar cuando se fusiona en la rama principal con confirmaciones entre

Como consecuencia:

  • Forbid human se compromete a la rama principal, automáticamente si puede, pero también como parte del proceso oficial si hay una laguna o si no es técnicamente posible hacer cumplir esto

    Al menos, puedes asegurarte de que nadie lo hará involuntariamente, o sin malicia, una vez que sea una regla básica. No debes intentar hacerlo nunca.

  • Tendrás que elegir entre:

    • Una única rama de ensayo, que hará que las fusiones exitosas fuesen fallidas si falla una compilación anterior que aún no se compila y no se prueba

      Al menos, sabrá qué combinación falló y tendrá a alguien que la corrija, pero las combinaciones intermedias no se pueden rastrear de manera trivial (por el sistema de control de versiones) para los resultados de futuras compilaciones y pruebas.

      Puedes ver la anotación del archivo (o culpar), pero a veces un cambio en un archivo (por ejemplo, la configuración) generará errores en lugares inesperados. Sin embargo, este es un evento bastante raro.

    • Varias ramas intermedias, que permitirán que las combinaciones exitosas no conflictivas lleguen a la rama principal

      Incluso en el caso de que alguna otra rama de ensayo tenga no en conflicto que produzcan fusiones. La trazabilidad es un poco mejor, al menos en el caso de que una fusión no esperaba un cambio que afecte a otra fusión. Pero, de nuevo, esto es lo suficientemente raro como para no preocuparse por cada día o cada semana.

      Para tener combinaciones no conflictivas la mayor parte del tiempo, es importante dividir las ramas de estadificación de manera sensata, por ejemplo. por equipo, por capa o por componente / proyecto / sistema / solución (cualquiera que sea su nombre).

      Si, mientras tanto, la rama principal se reenvió a otra combinación, debes fusionarla nuevamente. Con suerte, este no es un problema con fusiones no conflictivas o con muy pocos conflictos.

En comparación con los registros programados, la ventaja aquí es que tiene una garantía de una sucursal principal en funcionamiento, ya que solo se permite que la sucursal principal avance, no fusionar automáticamente sus cambios con lo que se haya confirmado en el medio. Así que el tercer punto es la diferencia esencial.

    
respondido por el acelent 25.10.2014 - 17:51
2

Prefiero que las "pruebas unitarias aprobadas" sean una puerta para enviar el código. Sin embargo, para que funcione, necesitarás algunas cosas.

Necesitará un marco de compilación que almacene en caché los artefactos.

Necesitará un marco de prueba que almacene en caché el estado de la prueba de las ejecuciones (exitosas) de prueba, con cualquier artefacto (s) dado.

De esa manera, los registros de entrada con las pruebas unitarias serán rápidas (la verificación cruzada desde la fuente hasta el artefacto que se construyó cuando el desarrollador verificó las pruebas antes del registro), se bloquearán aquellos con pruebas unitarias fallidas y los desarrolladores que convenientemente se olviden de revisar las compilaciones antes de comprometerse se les recordará que lo hagan la próxima vez, porque el ciclo de compilación y prueba es largo.

    
respondido por el Vatine 27.10.2015 - 14:03
1

Yo diría que depende del proyecto y del alcance de las pruebas automatizadas que se ejecutan en el "commit".

Si las pruebas que te gusta ejecutar en el activador de check-in son realmente rápidas, o si el flujo de trabajo del desarrollador forzará algún trabajo administrativo después de tal check-in de todos modos, creo que no debería importar mucho y forzar a los desarrolladores Solo para revisar las cosas que ejecutan absolutamente las pruebas más básicas. (Supongo que solo ejecutaría las pruebas más básicas en dicho activador).

Y creo que, si la velocidad / flujo de trabajo lo permite, es bueno no enviar cambios a otros desarrolladores que fallan en las pruebas, y solo sabes si fallan si los ejecutas.

Escribes "commit ... to remote branch" en la pregunta, lo que implicaría para mí que esto (a) no es algo que un desarrollador hace cada pocos minutos, por lo que una pequeña espera puede ser muy aceptable y (b) que después de dicha confirmación, los cambios del código pueden afectar a otros desarrolladores, por lo que es posible que se realicen comprobaciones adicionales.

Estoy de acuerdo con las otras respuestas en "no hagas que tus desarrolladores jueguen los pulgares mientras esperan" para una operación de este tipo.

    
respondido por el Martin Ba 24.10.2014 - 21:13
1

Las confirmaciones rotas no se deben permitir en troncal , porque troncal es lo que puede entrar en producción. Por lo tanto, debe asegurarse de que hay una puerta de enlace que deben pasar antes de estar en troncal . Sin embargo, las confirmaciones rotas pueden ser totalmente correctas en un repositorio siempre y cuando no esté en el tronco.

Por otro lado, requerir que los desarrolladores esperen / solucionen los problemas antes de enviar cambios al repositorio tiene varios inconvenientes.

Algunos ejemplos:

  • En TDD, es común confirmar y enviar pruebas fallidas para nuevas funciones antes de comenzar a implementar la función
  • Lo mismo ocurre con la notificación de errores al cometer e impulsar una prueba que falla
  • Al presionar el código incompleto, 2 o más personas pueden trabajar en una función en paralelo fácilmente
  • Su infraestructura de CI puede tomar su tiempo en la verificación, pero el desarrollador no tiene que esperar
respondido por el tkruse 26.07.2018 - 12:31

Lea otras preguntas en las etiquetas