Tipos de pruebas unitarias basadas en la utilidad

13

Desde el punto de vista del valor, veo dos grupos de pruebas de unidad en mi práctica:

  1. Pruebas que prueban alguna lógica no trivial. Escribiéndolos (ya sea antes implementación o después) revela algunos problemas / errores potenciales y ayuda a tener confianza en caso de que la lógica se cambie en el futuro.
  2. Pruebas que prueban alguna lógica muy trivial. Esas pruebas más como Código de documento (normalmente con simulacros) que lo prueba. El mantenimiento el flujo de trabajo de esas pruebas no es "alguna lógica cambió, la prueba se volvió roja - gracias a Dios, escribí esta prueba" pero "cambió un código trivial, la prueba se volvió falsa negativa - debo mantener (reescribir) la prueba sin obtener ningún beneficio ". La mayoría de las veces esas pruebas no son Vale la pena mantener (excepto motivos religiosos). Y segun mi experiencia en muchos sistemas, esas pruebas son como el 80% de todas las pruebas.

Estoy tratando de averiguar qué piensan otros tipos sobre el tema de las pruebas unitarias de separación por valor y cómo corresponde a mi separación. Pero lo que más veo es la propaganda TDD a tiempo completo o la propaganda de pruebas son inútiles, simplemente escribe el código. Estoy interesado en algo en el medio. Sus propios pensamientos o referencias a artículos / documentos / libros son bienvenidos.

    
pregunta SiberianGuy 07.04.2014 - 20:15

5 respuestas

13

Creo que es natural encontrar una división dentro de las pruebas unitarias. Hay muchas opiniones diferentes sobre cómo hacerlo correctamente y, naturalmente, todas las demás opiniones son inherentemente equivocadas . Hay unos cuantos artículos sobre DrDobbs recientemente que exploran este tema al que vinculo al final de mi respuesta.

El primer problema que veo con las pruebas es que es fácil hacerlas mal. En mi clase de C ++ de la universidad fuimos expuestos a pruebas de unidad tanto en el primer como en el segundo semestre. No sabíamos nada acerca de la programación en general en ninguno de los semestres, estábamos tratando de aprender los fundamentos de la programación a través de C ++. Ahora imagine que les dice a los estudiantes: "¡Oh, oye, escribiste una pequeña calculadora de impuestos anual! Ahora escribe algunas pruebas unitarias para asegurarte de que funciona correctamente". Los resultados deberían ser obvios, todos fueron horribles, incluidos mis intentos.

Una vez que admitas que apestas en las pruebas de unidad de escritura y deseas mejorar, pronto te enfrentarás con estilos de prueba modernos o diferentes metodologías. Al probar las metodologías me refiero a prácticas como la prueba en primer lugar o lo que hace Andrew Binstock de DrDobbs, que es escribir las pruebas junto con el código. Ambos tienen sus pros y sus contras y me niego a entrar en cualquier detalle subjetivo porque eso incitará una guerra de fuego. Si no está confundido acerca de qué metodología de programación es mejor, entonces tal vez el estilo de prueba haga el truco. ¿Debería usar TDD, BDD, pruebas basadas en la propiedad? JUnit tiene conceptos avanzados llamados teorías que desdibujan la línea entre TDD y las pruebas basadas en propiedades. ¿Cuál usar cuando?

tl; dr Es fácil equivocarse en las pruebas, tiene opiniones increíbles y no creo que ninguna metodología de prueba sea intrínsecamente mejor, siempre que se utilicen de manera diligente y profesional dentro del contexto que Además, las pruebas son, en mi opinión, una extensión de las aseveraciones o las pruebas de validez que se utilizaron para garantizar un enfoque de desarrollo ad-hoc a prueba de fallos que ahora es mucho, mucho más fácil.

Para una opinión subjetiva, prefiero escribir "fases" de las pruebas, por falta de una mejor frase. Escribo pruebas unitarias que evalúan las clases de forma aislada, utilizando simulacros cuando es necesario. Estos probablemente serán ejecutados con JUnit o algo similar. Luego escribo pruebas de integración o aceptación, que se ejecutan por separado y generalmente solo unas pocas veces al día. Estos son su caso de uso no trivial. Usualmente uso BDD ya que es bueno expresar características en lenguaje natural, algo que JUnit no puede proporcionar fácilmente.

Por último, los recursos. Estos presentarán opiniones conflictivas centradas principalmente en pruebas de unidad en diferentes idiomas y con diferentes marcos. Deben presentar la brecha en ideología y metodología al tiempo que te permiten crear tu propia opinión, siempre y cuando no haya manipulado demasiado la tuya :)

[1] The Corruption of Agile por Andrew Binstock

[2] Respuesta a las respuestas del artículo anterior

[3] Respuesta a la corrupción de Agile por el tío Bob

[4] Respuesta a la corrupción de Agile por Rob Myers

[5] ¿Por qué molestarse con las pruebas de pepino?

[6] Lo estás diciendo mal

[7] Alejándose de las herramientas

[8] Comentario sobre 'Kata de números romanos con comentario'

[9] Kata de números romanos con comentario

    
respondido por el IAE 07.04.2014 - 20:44
5

Creo que es importante realizar pruebas de ambos tipos y usarlas cuando sea apropiado.

Como has dicho, hay dos extremos y, sinceramente, tampoco estoy de acuerdo con ninguno de ellos.

La clave es que las pruebas unitarias tienen que cubrir las normas y requisitos empresariales . Si hay un requisito de que el sistema debe hacer un seguimiento de la edad de una persona, escriba pruebas "triviales" para asegurarse de que la edad sea un número entero no negativo. Está probando el dominio de datos requerido por el sistema: aunque es trivial, tiene un valor porque está aplicando los parámetros del sistema .

Igualmente con pruebas más complejas, tienen que aportar valor. Claro, puede escribir una prueba que valide algo que no es un requisito pero que debe ser implementado en una torre de marfil en algún lugar, pero ese es un tiempo mejor dedicado a escribir pruebas que validan los requisitos que el cliente le está pagando. Por ejemplo, ¿por qué escribir una prueba que valida su código puede lidiar con un flujo de entrada que se agota, cuando los únicos flujos son de archivos locales, no de la red?

Creo firmemente en las pruebas unitarias y uso TDD donde sea que tenga sentido. Las pruebas unitarias sin duda aportan valor en forma de mayor calidad y comportamiento "fallido" al cambiar el código. Sin embargo, también hay que tener en cuenta la antigua regla 80/20 . En algún momento, alcanzará rendimientos decrecientes cuando escriba las pruebas, y tendrá que pasar a un trabajo más productivo, incluso si se tiene algún valor medible al escribir más pruebas.

    
respondido por el user22815 09.04.2014 - 21:57
4

Aquí está mi opinión: todas las pruebas tienen costos:

  • tiempo y esfuerzo iniciales:
    • piensa en qué probar y cómo probarlo
    • implemente la prueba y asegúrese de que está probando lo que se supone que debe
  • mantenimiento continuo
    • asegurarse de que la prueba siga haciendo lo que se supone que debe hacer, ya que el código evoluciona de forma natural
  • ejecutando la prueba
    • tiempo de ejecución
    • analizando los resultados

También pretendemos que todas las pruebas proporcionen beneficios (y, según mi experiencia, casi todas las pruebas brindan beneficios):

  • especificación
  • resaltar casos de esquina
  • prevenir la regresión
  • verificación automática
  • ejemplos de uso de API
  • cuantificación de propiedades específicas (tiempo, espacio)

Entonces es bastante fácil ver que si escribes un montón de pruebas, probablemente tendrán algún valor. Cuando esto se complica es cuando empiezas a comparar ese valor (que, por cierto, es posible que no conozcas de antemano, si descartas el código, las pruebas de regresión pierden su valor) al costo.

Ahora, su tiempo y esfuerzo son limitados. Le gustaría elegir hacer aquellas cosas que brindan el mayor beneficio al menor costo. Y creo que es algo muy difícil de hacer, entre otras cosas porque puede requerir un conocimiento que uno no tiene o sería costoso obtener.

Y ese es el verdadero problema entre estos diferentes enfoques. Creo que todos han identificado estrategias de prueba que son beneficiosas. Sin embargo, cada estrategia tiene diferentes costos y beneficios en general. Además, los costos y beneficios de cada estrategia dependerán en gran medida de los aspectos específicos del proyecto, el dominio y el equipo. En otras palabras, puede haber múltiples mejores respuestas.

En algunos casos, el bombeo de código sin pruebas puede proporcionar los mejores beneficios / costos. En otros casos, un conjunto de pruebas completo puede ser mejor. En otros casos, lo mejor es mejorar el diseño.

    
respondido por el user39685 09.04.2014 - 21:06
2

¿Qué es una prueba de unidad , realmente? ¿Y hay realmente una gran dicotomía en juego aquí?

Trabajamos en un campo donde la lectura, literalmente, un poco después del final de un búfer puede bloquear totalmente un programa, o hacer que produzca un resultado totalmente inexacto, o como lo demuestra el reciente error TLS "HeartBleed", supuestamente Sistema seguro abierto sin producir ninguna evidencia directa de la falla.

Es imposible eliminar toda la complejidad de estos sistemas. Pero nuestro trabajo es, en la medida de lo posible, minimizar y gestionar esa complejidad.

¿Una prueba de unidad es una prueba que confirma, por ejemplo, que una reserva se publica correctamente en tres sistemas diferentes, se crea una entrada de registro y se envía una confirmación por correo electrónico?

Voy a decir no . Esa es una prueba de integración . Y los que definitivamente tienen su lugar, pero también son un tema diferente.

Una prueba de integración funciona para confirmar la función general de una "característica" completa. Pero el código detrás de esa característica se debe dividir en bloques de construcción simples y comprobables, también conocidos como "unidades".

Por lo tanto, una prueba de unidad debe tener un alcance muy limitado.

Lo que implica que el código probado por la prueba de la unidad debe tener un alcance muy limitado.

Lo que implica además que uno de los pilares de un buen diseño es dividir su problema complejo en partes más pequeñas y de un solo propósito (en la medida de lo posible) que puedan probarse en un aislamiento relativo entre sí.

Lo que terminas con es un sistema hecho de componentes de base confiables, y sabes si alguna de esas unidades fundamentales de código se rompe porque has escrito pruebas simples, pequeñas y de alcance limitado para decirte exactamente eso.

En muchos casos, es probable que también deba realizarse varias pruebas por unidad. Las pruebas en sí deben ser simples, probando una y solo una conducta en la medida de lo posible.

La noción de una "prueba unitaria" que prueba una lógica compleja, no trivial y elaborada es, creo, un poco de oxímoron.

Entonces, si ese tipo de desglose deliberado del diseño ha tenido lugar, entonces, ¿cómo es posible que una prueba unitaria en el mundo comience a producir falsos positivos, a menos que la función básica de la unidad de código probada haya cambiado ? Y si eso ha sucedido, es mejor que creas que hay algunos efectos dominantes no obvios en juego. Su prueba rota, la que parece estar produciendo un falso positivo, en realidad le advierte que algún cambio ha roto un círculo más amplio de dependencias en la base del código, y necesita ser examinado y corregido.

Es posible que algunas de esas unidades (muchas de ellas) deban probarse mediante el uso de objetos simulados, pero eso no significa que tenga que escribir pruebas más complejas o elaboradas.

Volviendo a mi ejemplo ideado de un sistema de reservas, realmente no puede enviar solicitudes a una base de datos de reservas en vivo o servicio de terceros (o incluso una instancia "dev" de él) cada vez que unidad prueba tu código.

Entonces, usas simulacros que presentan el mismo contrato de interfaz. Luego, las pruebas pueden validar el comportamiento de un fragmento de código determinista relativamente pequeño. El verde en toda la pizarra luego te dice que los bloques que conforman tu base no están rotos.

Pero la lógica de las pruebas de unidades individuales sigue siendo tan simple como sea posible.

    
respondido por el Craig 13.04.2014 - 02:03
1

Esto es, por supuesto, solo mi opinión, pero después de haber pasado los últimos meses aprendiendo programación funcional en fsharp (proveniente de un fondo de C #), me he dado cuenta de algunas cosas.

Como lo indicó el OP, generalmente hay 2 tipos de "pruebas de unidad" que vemos día a día. Pruebas que cubren las entradas y salidas de un método, que generalmente son las más valiosas, pero son difíciles de realizar para el 80% del sistema, que se trata menos de "algoritmos" y más de "abstracciones".

El otro tipo, está probando la interactividad de la abstracción, por lo general implica burla. En mi opinión, estas pruebas son principalmente necesarias debido al diseño de su aplicación. Omitiéndolos, te arriesgas a tener errores extraños y códigos de spagetti, porque la gente no piensa en su diseño correctamente a menos que se los obligue a hacer las pruebas primero (y aún así, normalmente lo estropean). El problema no es tanto la metodología de prueba, sino el diseño subyacente del sistema. La mayoría de los sistemas creados con lenguajes imperativos u OO tienen una dependencia inherente de los "efectos secundarios", al igual que "Haz esto, pero no me digas nada". Cuando confía en el efecto secundario, necesita probarlo, porque un requisito de negocio u operación suele ser parte de él.

Cuando diseña su sistema de una manera más funcional, donde evita crear dependencias en los efectos secundarios y evita cambios de estado / seguimiento a través de la inmutabilidad, le permite centrarse más en las pruebas de "entradas y salidas", que claramente prueban Más la acción, y menos cómo llegas allí. Se sorprenderá de lo que la inmutabilidad puede ofrecerle en términos de soluciones mucho más simples para los mismos problemas, y cuando ya no dependa de los "efectos secundarios", puede hacer cosas como paralelizar y programar de forma asíncrona sin casi ningún costo adicional.

Desde que comencé a codificar en Fsharp, no he necesitado un marco burlón para nada, e incluso he eliminado mi dependencia por completo de un contenedor IOC. Mis pruebas se basan en la necesidad y el valor del negocio, y no en capas de abstracción pesadas que suelen ser necesarias para lograr la composición en la programación imperativa.

    
respondido por el Mitchell Lee 13.04.2014 - 03:45

Lea otras preguntas en las etiquetas