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

43

Desde el punto de vista técnico, es posible agregar algunos ganchos de empuje previos / posteriores que ejecutarán pruebas unitarias antes de permitir que se fusionen algunos compromisos específicos con 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 repositorio) o es mejor simplemente no permitir que se produzcan confirmaciones "malas".

Me doy cuenta de que no estoy limitado con estas dos opciones. Por ejemplo, puedo permitir que todos los commits se ramifiquen y prueben antes de empujar merge commit a repo. Pero si tiene que elegir exactamente entre estas dos soluciones, ¿cuál elegirá y por qué razones exactas?

shabunc
fuente
1
relacionado: stackoverflow.com/questions/31681746
tkruse

Respuestas:

35

No, no lo es, por dos razones:

Velocidad

Los commits deben ser rápidos. Una confirmación que tarda 500 ms., Por ejemplo, es demasiado lenta y alentará a los desarrolladores a comprometerse 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 el precompromiso.

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

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

A partir de ese momento, puede pagar $ 10 000 adicionales por un mejor servidor que reducirá el tiempo, pero no mucho, o ejecutar 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 lo ayudará en proyectos a pequeña escala.

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 una confirmación. Posiblemente no puedan ejecutar miles de pruebas unitarias, pero pueden ejecutar cinco de diez.

    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 IDEs / plataformas / idiomas / marcos pueden tener una funcionalidad similar.

  • Mantenga 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 a menudo son increíblemente rápidos. Hacer análisis estático está bien tan pronto como se mantenga rápido, lo cual rara vez es el caso. Ejecutar 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 rompan la compilación (o cuando fallaron las pruebas unitarias, que es prácticamente lo mismo si considera un compilador como una herramienta que verifica algunos de los posibles errores que puede introducir en su código).

    Por ejemplo, ir a una página web para verificar 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 romper la compilación (o las pruebas de regresión fallidas) no está bien, 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 pidió enviar 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. ¿Esas razones ya se explicaron en el corredor de CI en el mismo servidor de GitLab?

Si, por otro lado, su idea es iniciar un proceso en el servidor de compilación desde el enlace previo a la confirmación, entonces disminuirá aún más las confirmaciones.

Arseni Mourzenko
fuente
44
Estoy de acuerdo, para eso está un servidor de compilación. Su control de origen es para administrar el código fuente, no para garantizar que su aplicación funcione.
Matthew
44
En casos extremos, las represalias pueden ser una herramienta de notificación necesaria al romper la compilación.
37
¿Medio segundo es demasiado lento? Esa es una gota en el cubo en comparación con dar una última mirada a lo que se está comprometiendo y luego pensar y escribir un comentario de confirmación apropiado.
Doval
55
@Doval: la diferencia es que cuando le das una última mirada o piensas en el comentario, eres proactivo y, por lo tanto, no estás esperando. Lo que importa no es el tiempo que pasa antes de escribir el último carácter en su IDE y el momento en que puede comenzar a escribir nuevamente una vez que finaliza la confirmación, sino cuánto espera. Es por eso que los compiladores deberían ser rápidos; no importa que pases mucho más tiempo leyendo y escribiendo código, porque cuando haces eso, no estás esperando, mientras que cuando el código se está compilando, estás tentado a cambiar a una actividad diferente.
Arseni Mourzenko
10
@Thomas: no se trata de distracción, sino de molestia. Del mismo modo, 100 ms. "tiene un impacto medible" sobre cómo las personas usan un sitio web. Mismo patrón aquí: 100 ms. no es nada comparado con el tiempo que pasa viendo un video de YouTube o iniciando su PC: conscientemente , no notará la diferencia entre 600 ms. y 700 ms. retrasar. Pero inconscientemente, influye en tu comportamiento. De la misma manera, los compromisos un poco más lentos lo desalientan de hacerlo temprano y con frecuencia.
Arseni Mourzenko
41

Déjame ser el que no esté de acuerdo con mis compañeros de respuesta.

Esto se conoce como check-ins cerrados en el mundo TFS, y espero en otros lugares. Cuando intenta registrarse en una sucursal con el check-in cerrado, el conjunto de estanterías se envía al servidor, lo que garantiza que sus cambios se compilen y se pasen las pruebas unitarias especificadas (léase: todas). Si no lo hacen, te notifica que eres un mono malo que rompió la construcción. Si lo hacen, los cambios pasan al control de fuente (¡sí!).

En mi experiencia, los registros privados son uno de los procesos más importantes para realizar pruebas unitarias exitosas y, por extensión, la calidad del software.

¿Por qué?

  • Debido a que los registros privados obligan a las personas a reparar pruebas rotas. Tan pronto como las pruebas interrumpidas se convierten en algo que las personas pueden hacer en lugar de lo que deben hacer, los ingenieros perezosos y / o las personas de negocios insistentes les quitan prioridad.
    • Cuanto más tiempo se rompa una prueba, más difícil (y costoso) será arreglarla.
  • Porque tan pronto como las personas deben ejecutar las pruebas en lugar de deben ejecutar las pruebas, ejecutar las pruebas son eludidas por los ingenieros perezosos / olvidadizo y / o personas de negocios agresivos.
  • Porque tan pronto como las pruebas unitarias afectan su tiempo de compromiso, las personas realmente comienzan a preocuparse por hacer sus pruebas unitarias . La velocidad importa. La reproducibilidad importa. La fiabilidad importa. El aislamiento importa.

Y, por supuesto, está el beneficio que mencionó originalmente: cuando ha cerrado los registros y un conjunto sólido de pruebas, cada conjunto de cambios es "estable". Guarda todos esos gastos generales (y el potencial de error) de "¿cuándo fue la última compilación correcta?" - Todas las compilaciones son lo suficientemente buenas como para desarrollarlas.

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

Telastyn
fuente
3
+1 Quiero agregar que muchos proyectos de código abierto tienen una clara distinción entre contribución y compromiso. Las razones para ello son muy similares a por qué existen los registros privados.
JensG
Un problema importante con el check-in cerrado es que inhibe la resolución colaborativa de problemas de código difícil. Esto es aún más significativo con los equipos distribuidos.
CuriousRabbit 01 de
2
@CuriousRabbit: ¿cómo te imaginas? Las personas rara vez se comprometen en colaboración, incluso si trabajan en colaboración. De lo contrario, las estanterías o las ramas de tareas sin fecha funcionan bien para eso sin obstaculizar al resto del equipo.
Telastyn
@ Telastyn– Shelvesets es un nuevo término para mí. Entiendo que esta es una opción de MS VS, que no es una opción para una gran cantidad de proyectos. En el campo del desarrollo de aplicaciones móviles, VS no es un jugador.
CuriousRabbit
3
@CuriousRabbit, ¿en serio? ¿Un equipo distribuido durante 17 horas se preocupa más por esperar 10 minutos para un compromiso que por la posibilidad de una construcción rota mientras la parte infractora está dormida? Eso parece ... Menos que óptimo.
Telastyn
40

Los commits deben correr rápido. Cuando confirmo un código, quiero que sea enviado al servidor. No quiero esperar unos minutos mientras ejecuta una batería de pruebas. Soy responsable de lo que envío al servidor y no necesito que nadie me cuide con ganchos de compromiso.

Dicho esto, una vez que llega al servidor, debe analizarse, probarse la unidad y construirse de inmediato (o dentro de un corto período de tiempo). Esto me alertaría sobre el hecho de que las pruebas unitarias están rotas, o no se compiló, o hice un desastre mostrado por las herramientas de análisis estático disponibles. Cuanto más rápido se haga esto (la construcción y el análisis), más rápido será mi respuesta y más rápido podré solucionarlo (los pensamientos no han desaparecido por completo de mi cerebro).

Entonces, no, no ponga pruebas y cosas así en commit hooks en el cliente. Si debe hacerlo, póngalos 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 avíseme adecuadamente a los problemas con el código. Pero, en primer lugar, no bloquee la confirmación.

También debo señalar que con algunas interpretaciones de Test Driven Development, uno debe verificar una prueba unitaria que se rompa primero . Esto demuestra y documenta que el error está presente. Luego, un registro posterior sería el código que corrige la prueba unitaria. 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 documenta el problema.

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

Comunidad
fuente
2
Commits should run fast.¿Cuáles son los beneficios de esto? Tengo curiosidad porque actualmente utilizamos registros cerrados. Por lo general, mis registros son una acumulación de aproximadamente una hora de trabajo, por lo que esperar 5 minutos no es gran cosa. De hecho, me he dado cuenta que sus normalmente los momentos en los que estoy en una carrera que la acumulación de validación es más útil para la captura de errores tontos (como resultado de correr)
Justin
1
@Justin Una espera de cinco minutos es una espera de cinco minutos sin importar dónde esté. Uno no debería necesitar romper espadas nerf cada vez que haces un commit. Y no es raro para mí dividir una hora o más de trabajo en múltiples confirmaciones que son unidades conceptuales entre sí: "confirmar el código de servicio de descanso" seguido de "confirmar el código para la página del cliente servido" como dos confirmaciones separadas (sin mencionar el ajuste css como otro commit). Si cada uno de estos tardó 5 minutos en ejecutarse, 1/6 de mi tiempo lo paso esperando las pruebas. Esto llevaría a confirmaciones de resumen de mayor tamaño que son más difíciles de rastrear errores en.
¿5 minutos de espera para ejecutar pruebas unitarias? Siento que los proyectos en los que están trabajando deberían dividirse en componentes más pequeños.
user441521
@Justin catching silly mistakes (as a result of rushing)exactamente. apresurarse en general es una mala práctica en ingeniería de software. Robert C. Martin recomienda escribir código como hacer una cirugía youtube.com/watch?v=p0O1VVqRSK0
Jerry Joseph
10

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

Sin embargo, la solución particular de commit hooks no es un buen plan.

  1. El desarrollador tiene que esperar a que se ejecuten las pruebas mientras se compromete. Si el desarrollador tiene que esperar en su estación de trabajo a 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 terminaron fallando.
  2. Los desarrolladores pueden querer cometer código roto en una rama. En una tarea más grande, la versión para desarrolladores del código 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 pueda usar el control de versiones para seguir su progreso.
  3. Hay buenas razones ocasionales para omitir el proceso y omitir las pruebas.
Winston Ewert
fuente
2
El n. ° 1 se evita al permitir que los desarrolladores se registren en una sucursal personal o en un repositorio local. Es solo cuando un desarrollador quiere su código en algún lugar que otros desarrolladores pueden ver que las pruebas unitarias deben ejecutarse. Al igual que con el n. ° 1, el n. ° 2 se obvia solo con ganchos a las ramas principales. El n. ° 3 se obvia por el hecho de que A) Cualquier característica de este tipo se puede deshabilitar, aunque sea una molestia (y debería ser una molestia) y B) Se pueden deshabilitar las pruebas de unidad de falla individual.
Brian
@Brian, estoy totalmente de acuerdo, puedes hacer que esto funcione. Pero tratar de hacerlo bloqueando el enlace de confirmación del lado del servidor no va a funcionar.
Winston Ewert
Buenos puntos. Breaking the build is simply too costly in terms of lost time for all engineers on the projectSugeriría usar algún tipo de herramienta de notificación de compilación para evitar que todos los ingenieros terminen perdiendo tiempo en cada compilación rota
Jerry Joseph
3

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

Si desea tener una base de código que garantice que no fallarán las pruebas, podría desarrollar en ramas de características y hacer solicitudes de extracción en maestro. Luego puede definir condiciones previas para aceptar esas solicitudes de extracción. El beneficio es que puede presionar muy rápido y las pruebas se ejecutan en segundo plano.

Yogu
fuente
2

Tener que esperar a que la compilación y las pruebas sean 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 el sentido de los registros cerrados en TFS, pero que es generalizable a cualquier sistema de control de versiones con ramas, aunque usaré principalmente términos git:

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

  • Configure un enlace que inicie o ponga en cola una compilación y pruebe las confirmaciones realizadas en la rama provisional, pero eso no hace que el confirmador espere

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

    Nota: no combine automáticamente en la rama principal, porque la combinación probada, si no es una combinación directa desde el punto de vista de la rama principal, puede fallar cuando se combina en la rama principal con confirmaciones intermedias

Como consecuencia:

  • Prohíba los compromisos humanos con 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 factible hacer cumplir esto.

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

  • Tendrás que elegir entre:

    • Una sola rama de preparación, que hará que las fusiones de otro modo exitosas realmente fallen si falla una fusión anterior pero no compilada y no probada

      Al menos, sabrá qué combinación falló y haga que alguien la corrija, pero las combinaciones intermedias no son trivialmente rastreables (por el sistema de control de versiones) para los resultados de otras compilaciones y pruebas.

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

    • Múltiples ramas intermedias, que permitirán que las fusiones exitosas no conflictivas lleguen a la rama principal

      Incluso en el caso de que alguna otra rama provisional tenga fusiones no conflictivas . La trazabilidad es un poco mejor, al menos en el caso de que una fusión no esperara un cambio que afecte a otra fusión. Pero, de nuevo, esto es lo suficientemente raro como para no preocuparse todos los días o todas las semanas.

      Para tener fusiones no conflictivas la mayor parte del tiempo, es importante dividir las ramas de puesta en escena con sensatez, por ejemplo, por equipo, por capa o por componente / proyecto / sistema / solución (lo que sea que lo nombre).

      Si mientras tanto, la rama principal se reenvió a otra fusión, debe volver a fusionarse. Con suerte, esto no es un problema con fusiones no conflictivas o con muy pocos conflictos.

En comparación con los registros cerrados, la ventaja aquí es que tiene la garantía de una rama principal que funciona, porque la rama principal solo puede avanzar, no fusionar automáticamente sus cambios con lo que se haya comprometido en el medio. Entonces el tercer punto es la diferencia esencial.

acelente
fuente
Pero el objetivo de tener una rama funcional no es (generalmente) asegurar que tenga una versión estable, sino reducir la agitación causada por los desarrolladores que trabajan juntos en el mismo código.
Telastyn
No si tienes un gran repositorio con grandes equipos distribuidos globalmente. Por ejemplo, Microsoft usa un enfoque similar y más estratificado con Windows.
Acelerado
2

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

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

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

De esa manera, los registros con las pruebas unitarias aprobadas serán rápidos (verificación cruzada desde la fuente hasta el artefacto que fue construido cuando el desarrollador verificó las pruebas antes del registro), aquellos con pruebas unitarias fallidas serán bloqueados y los desarrolladores que convenientemente, olvide verificar las compilaciones antes de confirmar, se le recomendará que recuerde hacerlo la próxima vez, porque el ciclo de compilación y prueba es largo.

Vatine
fuente
1

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

Si las pruebas que desea ejecutar en el desencadenador 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 obligar a los desarrolladores a verificar solo en cosas que ejecutan absolutamente las pruebas más básicas. (Supongo que solo ejecutaría las pruebas más básicas en dicho disparador).

Y creo que, si la velocidad / el flujo de trabajo lo permiten, es bueno no enviar cambios a otros desarrolladores que no pasan las pruebas, y solo se sabe si fallan si los ejecuta.

Escribe "commit ... to remote branch" en la pregunta, lo que implicaría que esto no es (a) algo que un desarrollador hace cada pocos minutos, por lo que una pequeña espera puede ser muy aceptable, y (b) que después Tal confirmación de los cambios en el código puede afectar a otros desarrolladores, por lo que pueden ser necesarios controles adicionales.

Puedo estar de acuerdo con las otras respuestas en "no hagas que tus desarrolladores tomen los pulgares mientras esperan" para tal operación.

Martin Ba
fuente
1

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

Por otro lado, exigir a los desarrolladores que esperen / solucionen problemas antes de enviar cambios al repositorio tiene una serie de inconvenientes.

Algunos ejemplos:

  • En TDD, es común confirmar y enviar pruebas fallidas para nuevas características antes de comenzar a implementar la característica
  • Lo mismo ocurre con la notificación de errores al cometer y empujar una prueba fallida
  • Empujar código incompleto permite que 2 o más personas trabajen en una característica en paralelo fácilmente
  • Su infraestructura de CI puede tomar su tiempo para verificar, pero el desarrollador no tiene que esperar
tkruse
fuente