¿Cómo se debe organizar el código de prueba unitaria de C ++ para obtener la máxima eficacia de la prueba unitaria?


47
  • Esta pregunta no se trata de marcos de prueba de unidad.
  • Esta pregunta no se trata de escribir pruebas unitarias.
  • Esta pregunta es sobre dónde poner el código UT escrito y cómo / cuándo / dónde compilarlo y ejecutarlo.

Al trabajar eficazmente con el código heredado , Michael Feathers afirma que

buenas pruebas unitarias ... corre rápido

y eso

Una prueba unitaria que tarda 1/10 de segundo en ejecutarse es una prueba unitaria lenta.

Creo que estas definiciones tienen sentido. También creo que implican que debe mantener un conjunto de Pruebas unitarias y un conjunto de Pruebas de código que toman más tiempo por separado, pero supongo que ese es el precio que paga por llamar a algo como Prueba unitaria si se ejecuta (muy) rápido .

Obviamente, el problema en C ++ es que para "ejecutar" su ( s ) Prueba ( s ) de Unidad , debe:

  1. Edite su código (producción o prueba unitaria, según el "ciclo" en el que se encuentre)
  2. Compilar
  3. Enlazar
  4. Iniciar unidad de prueba Ejecutable ( s )

Editar (después de una votación cerrada) : antes de entrar en detalles, intentaré resumir el punto aquí:

¿Cómo se puede organizar eficazmente el código de prueba unitaria de C ++, de modo que sea eficiente editar el código (prueba) y ejecutar el código de prueba?


El primer problema es decidir dónde colocar el código de Prueba de Unidad para que:

  • es "natural" editarlo y verlo en combinación con el código de producción asociado.
  • es fácil / rápido comenzar el ciclo de compilación de la unidad que está cambiando actualmente

El segundo problema relacionado es qué compilar para que la retroalimentación sea instantánea.

Opciones extremas:

  • Cada Unidad-Prueba-Prueba-Unidad vive en un archivo cpp separado y este archivo cpp se compila + enlaza por separado (junto con el archivo de unidad de código fuente que prueba) a un único ejecutable que luego ejecuta esta Prueba unitaria.
    • (+) Esto minimiza el tiempo de inicio (¡compilación + enlace!) Para la Unidad de Prueba individual.
    • (+) La prueba se ejecuta muy rápido, ya que solo prueba una unidad.
    • (-) La ejecución de toda la suite necesitará iniciar una gran cantidad de procesos. Puede ser un problema para gestionar.
    • (-) La sobrecarga del inicio del proceso se hará visible
  • El otro lado sería tener, aún, un archivo cpp por prueba, pero todos los archivos cpp de prueba (¡junto con el código que prueban!) Están vinculados en un ejecutable (por módulo / por proyecto / elija su elección).
    • (+) El tiempo de compilación aún estaría bien, ya que solo se compilará el código modificado.
    • (+) Ejecutar toda la suite es fácil, ya que solo hay un exe para ejecutar.
    • (-) La suite tardará años en vincularse, ya que cada compilación de cualquier objeto activará un reenlace.
    • (-) (?) El traje tardará más en ejecutarse, aunque si todas las pruebas unitarias son rápidas, el tiempo debería estar bien.

Entonces, ¿cómo se manejan las pruebas unitarias C ++ del mundo real ? Si solo ejecuto esas cosas cada noche / hora, la segunda parte realmente no importa, pero la primera parte, es decir, cómo "acoplar" el código UT al código de producción, de modo que sea "natural" para los desarrolladores mantener ambos en El enfoque siempre importa, creo. (Y si los desarrolladores tienen el código UT en foco, querrán ejecutarlo, lo que nos lleva de vuelta a la segunda parte).

¡Historias y experiencias del mundo real apreciadas!

Notas:

  • Esta pregunta deja intencionalmente una plataforma no especificada y un sistema de creación / proyecto.
  • Preguntas etiquetadas UT & C ++ es un excelente lugar para comenzar, pero desafortunadamente muchas preguntas, y especialmente las respuestas, se centran demasiado en los detalles o en marcos específicos.
  • Hace un tiempo, respondí una pregunta similar sobre la estructura de las pruebas de unidad de refuerzo. Encuentro que esta estructura es insuficiente para las pruebas de unidad "reales" y rápidas. Y encuentro la otra pregunta demasiado estrecha, de ahí esta nueva pregunta.

66
El lenguaje de C ++ introduce una complicación adicional de mover tantos errores como sea posible para compilar el tiempo: un buen conjunto de pruebas unitarias a menudo necesita poder probar que cierto uso no se compila.

3
@Closers: ¿Podría proporcionar los argumentos que lo llevaron a cerrar esta pregunta?
Luc Touraille

No entiendo por qué esto tuvo que cerrarse. :-(¿Dónde se supone que debe buscar respuestas a esas preguntas si no es en este foro?

2
@ Joe: ¿Por qué necesito una prueba unitaria para encontrar si algo se compilará cuando el compilador me lo diga de todos modos?
David Thornley

2
@David: Porque quieres asegurarte de que no se compila . Un ejemplo rápido sería un objeto de canalización que acepta una entrada y produce una salida que Pipeline<A,B>.connect(Pipeline<B,C>)debe compilarse mientras Pipeline<A,B>.connect(Pipeline<C,D>)que no debe compilarse: el tipo de salida de la primera etapa es incompatible con el tipo de entrada de la segunda etapa.
sebastiangeiger

Respuestas:


6

Tenemos todas las pruebas unitarias (para un módulo) en un ejecutable. Las pruebas se ponen en grupos. Puedo ejecutar una sola prueba (o algunas pruebas) o un grupo de pruebas especificando un nombre (prueba / grupo) en la línea de comando del corredor de prueba. El sistema de compilación puede ejecutar el grupo "Compilar", el departamento de prueba puede ejecutar "Todos". El desarrollador puede poner algunas pruebas en un grupo como "BUG1234" con 1234 como el número de seguimiento del problema en el que está trabajando.


6

Primero, no estoy de acuerdo con "1) Edite su código (de producción) y su Prueba de Unidad". Debe modificar solo uno a la vez, de lo contrario, si el resultado cambia, no sabrá cuál lo causó.

Me gusta poner pruebas unitarias en un árbol de directorios que sombrea el árbol principal. Si tengo /sources/componentA/alpha/foo.ccy /objects/componentA/beta/foo.o, entonces quiero algo como /UTest_sources/componentA/alpha/test_foo.ccy /UTest_objects/componentA/beta/test_foo.o. Utilizo el mismo árbol de sombra para los objetos cortos / simulados y cualquier otra fuente que necesiten las pruebas. Habrá algunos casos extremos, pero este esquema simplifica mucho las cosas. Una buena macro de editor puede extraer la fuente de prueba junto con la fuente del sujeto sin esfuerzo. Un buen sistema de compilación (p. Ej., GNUMake) puede compilar ambos y ejecutar la prueba con un comando (p make test_foo. Ej. ), Y puede administrar una gran cantidad de tales procesos, solo aquellos cuyas fuentes han cambiado desde la última vez que se probaron, con bastante facilidad (tengo Nunca encontramos que la sobrecarga de iniciar estos procesos sea un problema, es O (N)).

En el mismo marco, puede tener pruebas a mayor escala (ya no pruebas unitarias) que vinculan muchos objetos y ejecutan muchas pruebas. El truco consiste en ordenar estas pruebas según el tiempo que tardan en compilarse / ejecutarse, y trabajarlas en su programación diaria en consecuencia. Ejecute la prueba de un segundo o menos cuando lo desee; comience la prueba de diez segundos y estire; prueba de cinco minutos y tomar un descanso; prueba de media hora e ir a almorzar; prueba de seis horas y vete a casa. Si descubre que está perdiendo mucho tiempo, por ejemplo, volver a vincular una prueba enorme después de cambiar solo un archivo pequeño, lo está haciendo mal, incluso si el enlace fuera instantáneo, todavía estaría ejecutando una prueba larga cuando No fue requerido.


anuncio (1) - sí, que fue redactado descuidadamente
Martin Ba
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.