¿Es suficiente usar pruebas de aceptación e integración en lugar de pruebas unitarias?

62

Breve introducción a esta pregunta. He usado TDD ahora y últimamente BDD por más de un año. Utilizo técnicas como burlarse para que escribir mis exámenes sea más eficiente. Últimamente he comenzado un proyecto personal para escribir un pequeño programa de administración de dinero para mí. Como no tenía código heredado, fue el proyecto perfecto para comenzar con TDD. Desafortunadamente, no experimenté tanto la alegría de TDD. Incluso estropeó mi diversión tanto que he renunciado al proyecto.

¿Cual fue el problema? Bueno, he usado el enfoque similar a TDD para permitir que las pruebas / requisitos evolucionen el diseño del programa. El problema era que más de la mitad del tiempo de desarrollo de las pruebas de escritura / refactorización. Así que al final no quería implementar más funciones porque necesitaría refactorizar y escribir en muchas pruebas.

En el trabajo tengo mucho código heredado. Aquí escribo más y más pruebas de integración y aceptación y menos pruebas unitarias. Esto no parece ser un mal enfoque ya que los errores son detectados principalmente por las pruebas de aceptación e integración.

Mi idea era que al final podría escribir más pruebas de integración y aceptación que pruebas unitarias. Como dije para detectar errores, las pruebas unitarias no son mejores que las pruebas de integración / aceptación. Las pruebas unitarias también son buenas para el diseño. Como solía escribir muchos de ellos, mis clases siempre están diseñadas para ser comprobables. Además, el enfoque para permitir que las pruebas / requisitos guíen el diseño conduce en la mayoría de los casos a un mejor diseño. La última ventaja de las pruebas unitarias es que son más rápidas. He escrito suficientes pruebas de integración para saber que pueden ser casi tan rápidas como las pruebas unitarias.

Después de buscar en la web, descubrí que hay ideas muy similares a las mías mencionadas aquí y allá . ¿Qué piensas de esta idea?

Editar

Respondiendo a las preguntas, un ejemplo en el que el diseño era bueno, pero necesitaba una gran refactorización para el siguiente requisito:

Al principio había algunos requisitos para ejecutar ciertos comandos. Escribí un analizador de comandos extensible, que analizaba los comandos de algún tipo de símbolo del sistema y llamaba al correcto en el modelo. El resultado se representó en una clase de modelo de vista: Primer diseño

No había nada malo aquí. Todas las clases eran independientes entre sí y podía agregar fácilmente nuevos comandos, mostrar nuevos datos.

El siguiente requisito era que cada comando debería tener su propia representación de vista, algún tipo de vista previa del resultado del comando. Rediseñé el programa para lograr un mejor diseño para el nuevo requisito: Segundo diseño

Esto también fue bueno porque ahora cada comando tiene su propio modelo de vista y, por lo tanto, su propia vista previa.

La cuestión es que el analizador de comandos se cambió para usar un análisis basado en tokens de los comandos y se despojó de su capacidad para ejecutar los comandos. Cada comando tiene su propio modelo de vista y el modelo de vista de datos solo conoce el modelo de vista de comando actual que conoce los datos que se deben mostrar.

Todo lo que quería saber en este momento es si el nuevo diseño no rompió ningún requisito existente. No tuve que cambiar CUALQUIERA de mi prueba de aceptación. Tuve que refactorizar o eliminar casi CADA prueba unitaria, lo que fue una gran cantidad de trabajo.

Lo que quería mostrar aquí es una situación común que sucedió a menudo durante el desarrollo. No hubo ningún problema con los diseños antiguos o nuevos, simplemente cambiaron naturalmente con los requisitos: cómo lo entendí, esta es una ventaja de TDD, que el diseño evoluciona.

Conclusión

Gracias por todas las respuestas y discusiones. En resumen de esta discusión, he pensado en un enfoque que probaré con mi próximo proyecto.

  • En primer lugar, escribo todas las pruebas antes de implementar algo como siempre lo hice.
  • Para requisitos, primero escribo algunas pruebas de aceptación que prueban todo el programa. Luego escribo algunas pruebas de integración para los componentes donde necesito implementar el requisito. Si hay un componente que trabaje estrechamente con otro componente para implementar este requisito, también escribiría algunas pruebas de integración donde ambos componentes se prueban juntos. Por último, pero no menos importante, si tengo que escribir un algoritmo o cualquier otra clase con una permutación alta, por ejemplo, un serializador, escribiría pruebas unitarias para estas clases en particular. Todas las otras clases no se prueban, pero ninguna prueba unitaria.
  • Para errores, el proceso puede simplificarse. Normalmente un error es causado por uno o dos componentes. En este caso, escribiría una prueba de integración para los componentes que prueban el error. Si se relacionara con un algoritmo, solo escribiría una prueba unitaria. Si no es fácil detectar el componente donde se produce el error, escribiría una prueba de aceptación para localizar el error; esto debería ser una excepción.
Yggdrasil
fuente
Estas preguntas parecen abordar más el problema de por qué escribir pruebas en absoluto. Quiero discutir si escribir pruebas funcionales en lugar de pruebas unitarias podría ser un mejor enfoque.
Yggdrasil
Según mi lectura, las respuestas en preguntas duplicadas son principalmente sobre cuándo las pruebas que no son de unidad tienen más sentido
mosquito
Ese primer enlace es en sí mismo un duplicado. Creo que te refieres a: programmers.stackexchange.com/questions/66480/…
Robbie Dee
Las respuestas del enlace de Robbie Dee son aún más sobre por qué hacer la prueba.
Yggdrasil

Respuestas:

37

Está comparando naranjas y manzanas.

Pruebas de integración, pruebas de aceptación, pruebas de unidad, pruebas de comportamiento: todas son pruebas y todas te ayudarán a mejorar tu código, pero también son bastante diferentes.

Voy a repasar cada una de las diferentes pruebas en mi opinión y espero explicar por qué necesita una combinación de todas ellas:

Pruebas de integración:

Simplemente, compruebe que las diferentes partes componentes de su sistema se integran correctamente, por ejemplo, tal vez simule una solicitud de servicio web y verifique que el resultado regrese. En general, usaría datos estáticos reales (ish) y dependencias simuladas para garantizar que se puedan verificar de manera consistente.

Prueba de aceptacion:

Una prueba de aceptación debe correlacionarse directamente con un caso de uso comercial. Puede ser enorme ("las transacciones se envían correctamente") o pequeña ("el filtro filtra una lista con éxito"), no importa; lo que importa es que debe estar explícitamente vinculado a un requisito específico del usuario. Me gusta centrarme en estos para el desarrollo basado en pruebas porque significa que tenemos un buen manual de referencia de pruebas para historias de usuarios para que dev y qa verifiquen.

Pruebas unitarias:

Para pequeñas unidades discretas de funcionalidad que pueden o no componer una historia de usuario individual por sí misma, por ejemplo, una historia de usuario que dice que recuperamos a todos los clientes cuando accedemos a una página web específica puede ser una prueba de aceptación (simular golpear la web página y verificar la respuesta), pero también puede contener varias pruebas unitarias (verifique que los permisos de seguridad estén verificados, verifique que la conexión de la base de datos consulte correctamente, verifique que cualquier código que limite el número de resultados se ejecute correctamente): todas estas son "pruebas unitarias" eso no es una prueba de aceptación completa.

Pruebas de comportamiento:

Defina cuál debería ser el flujo de una aplicación en el caso de una entrada específica. Por ejemplo, "cuando no se puede establecer la conexión, verifique que el sistema vuelva a intentar la conexión". Nuevamente, es poco probable que sea una prueba de aceptación completa, pero aún así le permite verificar algo útil.

Todo esto está en mi opinión a través de mucha experiencia en pruebas de escritura; No me gusta centrarme en los enfoques de los libros de texto, sino centrarme en lo que le da valor a tus pruebas.

Miguel
fuente
En su definición, supongo que lo que quiero decir es escribir más pruebas de comportamiento (?) Que pruebas unitarias. La prueba unitaria para mí es una prueba que prueba una sola clase con todas las dependencias burladas. Hay casos en los que las pruebas unitarias son más útiles. Es decir, por ejemplo, cuando escribo un algoritmo complejo. Luego tengo muchos ejemplos con la salida esperada del algoritmo. Quiero probar esto a nivel de unidad porque en realidad es más rápido que la prueba de comportamiento. No veo el valor de probar una clase a nivel de unidad que solo tiene una mano llena de rutas a través de la clase que pueden probarse fácilmente mediante una prueba de comportamiento.
Yggdrasil
14
Personalmente, creo que las pruebas de aceptación son las más importantes, las pruebas de comportamiento son importantes cuando se prueban cosas como la comunicación, la fiabilidad y los casos de error, y las pruebas unitarias son importantes cuando se prueban características complejas pequeñas (los algoritmos serían un buen ejemplo de esto)
Michael
No soy tan firme con tu terminología. Programamos una suite de programación. Ahí soy responsable del editor gráfico. Mis pruebas prueban el editor con servicios simulados del resto de la suite y con una interfaz de usuario simulada. ¿Qué tipo de prueba sería esa?
Yggdrasil
1
Depende de lo que esté probando: ¿está probando características comerciales (pruebas de aceptación)? ¿Está probando la integración (pruebas de integración)? ¿Está probando lo que sucede cuando hace clic en un botón (pruebas de comportamiento)? ¿Estás probando algoritmos (pruebas unitarias)?
Michael
44
"No me gusta centrarme en los enfoques de los libros de texto, más bien, centrarme en lo que le da valor a tus exámenes" ¡Oh, qué cierto! La primera pregunta a hacer siempre es "¿qué problema resuelvo haciendo esto?". ¡Y un proyecto diferente puede tener un problema diferente para resolver!
Laurent Bourgault-Roy
40

TL; DR: siempre y cuando satisfaga sus necesidades, sí.

Llevo muchos años haciendo desarrollo de desarrollo impulsado por pruebas de aceptación (ATDD). Puede ser muy exitoso. Hay algunas cosas a tener en cuenta.

  • Las pruebas unitarias realmente ayudan a hacer cumplir el COI. Sin las pruebas unitarias, los desarrolladores tienen la responsabilidad de asegurarse de que cumplen con los requisitos de un código bien escrito (en la medida en que las pruebas unitarias conducen un código bien escrito)
  • Pueden ser más lentos y tener fallas falsas si en realidad está utilizando recursos que generalmente se burlan.
  • La prueba no señala el problema específico como lo harían las pruebas unitarias. Debe investigar más para corregir los errores de prueba.

Ahora los beneficios

  • Mucho mejor cobertura de prueba, cubre puntos de integración.
  • Asegura que el sistema en su conjunto cumpla con los criterios de aceptación, que es el punto principal del desarrollo de software.
  • Hace que los refactores grandes sean mucho más fáciles, más rápidos y más baratos.

Como siempre, depende de usted hacer el análisis y determinar si esta práctica es adecuada para su situación. A diferencia de muchas personas, no creo que haya una respuesta correcta idealizada. Dependerá de sus necesidades y requisitos.

dietbuddha
fuente
8
Excelente punto Es demasiado fácil convertirse en un pequeño cadete del espacio sobre las pruebas y escribir cientos de casos para obtener un cálido resplandor de satisfacción cuando "pasa" con gran éxito. En pocas palabras: si su software no hace lo que debe hacer desde el punto de vista del USUARIO, ha fallado la primera y más importante prueba.
Robbie Dee
Buen punto para señalar el problema específico. Si tengo un gran requisito, escribo pruebas de aceptación que prueban todo el sistema y luego escribo pruebas que prueban subtareas de ciertos componentes del sistema para lograr el requisito. Con esto, puedo identificar en la mayoría de los casos el componente donde se encuentra el defecto.
Yggdrasil
2
¿"Las pruebas unitarias ayudan a hacer cumplir el COI"? Estoy seguro de que se refería a DI allí en lugar de IoC, pero de todos modos, ¿por qué alguien querría exigir el uso de DI? Personalmente, encuentro que en la práctica DI conduce a no objetos (programación de estilo de procedimiento).
Rogério
¿Puede dar su opinión sobre la opción (IMO mejor) de hacer AMBAS pruebas de integración y unidad frente al argumento de solo hacer pruebas de integración? Su respuesta aquí es buena, pero parece enmarcar estas cosas como mutuamente excluyentes, lo que no creo que sean.
starmandeluxe
@starmandeluxe De hecho, no son mutuamente excluyentes. Más bien se trata del valor que desea obtener de las pruebas. Realizaría pruebas unitarias en cualquier lugar donde el valor excediera el costo de desarrollo / soporte de escribir las pruebas unitarias. ex. Ciertamente, probaría la función de interés compuesto en una aplicación financiera.
dietbuddha
18

Bueno, he usado el enfoque similar a TDD para permitir que las pruebas / requisitos evolucionen el diseño del programa. El problema era que más de la mitad del tiempo de desarrollo para escribir / refactorizar pruebas

Las pruebas unitarias funcionan mejor cuando la interfaz pública de los componentes para los que se utilizan no cambia con demasiada frecuencia. Esto significa, cuando los componentes ya están bien diseñados (por ejemplo, siguiendo los principios SÓLIDOS).

Por lo tanto, creer que un buen diseño simplemente "evoluciona" de "lanzar" muchas pruebas unitarias a un componente es una falacia. TDD no es "maestro" para un buen diseño, solo puede ayudar un poco a verificar que ciertos aspectos del diseño sean buenos (especialmente la capacidad de prueba).

Cuando sus requisitos cambian, y tiene que cambiar los componentes internos de un componente, y esto romperá el 90% de las pruebas de su unidad, por lo que debe refactorizarlas muy a menudo, entonces el diseño probablemente no fue tan bueno.

Entonces, mi consejo es: piense en el diseño de los componentes que ha creado y en cómo puede hacerlos más siguiendo el principio abierto / cerrado. La idea de este último es asegurarse de que la funcionalidad de sus componentes se pueda extender más tarde sin cambiarlos (y así no romper la API del componente utilizada por sus pruebas unitarias). Dichos componentes pueden (y deben) cubrirse mediante pruebas de prueba unitarias, y la experiencia no debe ser tan dolorosa como la ha descrito.

Cuando no se puede llegar a tal diseño de inmediato, las pruebas de aceptación e integración pueden ser un mejor comienzo.

EDITAR: a veces el diseño de sus componentes puede estar bien, pero el diseño de las pruebas de su unidad puede causar problemas . Ejemplo simple: desea probar el método "MyMethod" de la clase X y escribir

    var x= new X();
    Assert.AreEqual("expected value 1" x.MyMethod("value 1"));
    Assert.AreEqual("expected value 2" x.MyMethod("value 2"));
    // ...
    Assert.AreEqual("expected value 500" x.MyMethod("value 500"));

(suponga que los valores tienen algún tipo de significado).

Supongamos además que en el código de producción solo hay una llamada X.MyMethod. Ahora, para un nuevo requisito, el método "MyMethod" necesita un parámetro adicional (por ejemplo, algo así como context), que no se puede omitir. Sin pruebas unitarias, uno tendría que refactorizar el código de llamada en un solo lugar. Con las pruebas unitarias, uno tiene que refactorizar 500 lugares.

Pero la causa aquí no es que la unidad se pruebe a sí misma, es solo el hecho de que la misma llamada a "X.MyMethod" se repite una y otra vez, sin seguir estrictamente el principio "No te repitas (DRY). Así que la solución aquí es poner los datos de prueba y los valores esperados relacionados en una lista y ejecutar las llamadas a "MyMethod" en un bucle (o, si la herramienta de prueba admite las llamadas "pruebas de manejo de datos", para usar esa función). el número de lugares para cambiar en las pruebas unitarias cuando la firma del método cambia a 1 (en lugar de 500).

En su caso del mundo real, la situación puede ser más compleja, pero espero que se haga una idea: cuando las pruebas de su unidad usen una API de componentes para la que no sabe si puede estar sujeta a cambios, asegúrese de reducir el número de llamadas a esa API al mínimo.

Doc Brown
fuente
"Esto significa, cuando los componentes ya están bien diseñados": Estoy de acuerdo con usted, pero ¿cómo pueden los componentes ya estar diseñados si escribe las pruebas antes de escribir el código, y el código es el diseño? Al menos así es como he entendido TDD.
Giorgio el
2
@ Jorge: en realidad, no importa si escribes las pruebas primero o después. Diseño significa tomar decisiones sobre la responsabilidad de un componente, sobre la interfaz pública, sobre las dependencias (directas o inyectadas), sobre el tiempo de ejecución o el comportamiento del tiempo de compilación, sobre la mutabilidad, sobre los nombres, sobre el flujo de datos, el flujo de control, las capas, etc. el diseño también significa diferir algunas decisiones al último momento posible. La prueba unitaria puede mostrarle indirectamente si su diseño fue correcto: si tiene que refactorizar muchos de ellos después de que los requisitos cambien, probablemente no lo fue.
Doc Brown
@Giorgio: un ejemplo podría aclarar: digamos que tiene un componente X con un método "MyMethod" y 2 parámetros. Usando TDD, usted escribe X x= new X(); AssertTrue(x.MyMethod(12,"abc"))antes de implementar el método. Usando un diseño inicial, puede escribir class X{ public bool MyMethod(int p, string q){/*...*/}}primero y escribir las pruebas más tarde. En ambos casos, ha tomado la misma decisión de diseño. Si la decisión fue buena o mala, TDD no se lo informará.
Doc Brown
1
Estoy de acuerdo con usted: soy un poco escéptico cuando veo que TDD se aplica a ciegas con el supuesto de que producirá automáticamente un buen diseño. Además, a veces TDD se interpone en el camino si el diseño aún no está claro: me veo obligado a probar los detalles antes de tener una visión general de lo que estoy haciendo. Entonces, si entiendo correctamente, estamos de acuerdo. Creo que (1) las pruebas unitarias ayudan a verificar un diseño, pero el diseño es una actividad separada, y (2) TDD no siempre es la mejor solución porque necesita organizar sus ideas antes de comenzar a escribir pruebas y TDD puede retrasarlo esta.
Giorgio el
1
En breve, las pruebas unitarias pueden mostrar fallas en el diseño interno de un componente. La interfaz, las condiciones previas y posteriores deben conocerse de antemano; de lo contrario, no puede crear la prueba unitaria. Por lo tanto, el diseño del componente que hace el componente debe realizarse antes de que se pueda escribir la prueba unitaria. Cómo lo hace: el diseño de nivel inferior, el diseño detallado o el diseño interno o como quiera llamarlo, puede tener lugar después de que se haya escrito la prueba de la unidad.
Maarten Bodewes
9

Sí, claro que lo es.

Considera esto:

  • Una prueba unitaria es una prueba pequeña y específica que ejerce una pequeña pieza de código. Escribe muchos de ellos para lograr una cobertura de código decente, de modo que se prueben todos (o la mayoría de los bits incómodos).
  • Una prueba de integración es una prueba grande y amplia que ejercita una gran superficie de su código. Escribe algunos de ellos para lograr una cobertura de código decente, de modo que se prueben todos (o la mayoría de los bits incómodos).

Ver la diferencia general ...

El problema es uno de cobertura de código, si puede lograr una prueba completa de todo su código usando pruebas de integración / aceptación, entonces no hay problema. Tu código ha sido probado. Ese es el objetivo.

Creo que es posible que deba mezclarlos, ya que cada proyecto basado en TDD requerirá algunas pruebas de integración solo para asegurarse de que todas las unidades realmente funcionen bien juntas (sé por experiencia que una base de código probada al 100% no necesariamente funciona cuando los pones todos juntos!)

El problema realmente se reduce a la facilidad de probar, depurar las fallas y corregirlas. Algunas personas encuentran que sus pruebas unitarias son muy buenas para esto, son pequeñas y simples y las fallas son fáciles de ver, pero la desventaja es que tienes que reorganizar tu código para adaptarlo a las herramientas de prueba unitaria, y escribir muchas de ellas. Una prueba de integración es más difícil de escribir para cubrir una gran cantidad de código, y probablemente tendrá que usar técnicas como iniciar sesión para depurar cualquier falla (aunque, diría que tiene que hacer esto de todos modos, no puede fallar la prueba unitaria cuando en el sitio!).

Sin embargo, de cualquier manera, aún obtienes código probado, solo tienes que decidir qué mecanismo te conviene más. (Me gustaría mezclar un poco, probar unitariamente los algoritmos complejos e integrar la prueba del resto).

gbjbaanb
fuente
77
No es del todo cierto ... Con las pruebas de integración es posible tener dos componentes que tienen errores, pero sus errores se cancelan en la prueba de integración. No importa mucho hasta que el usuario final lo use de una manera en la que solo se use uno de estos componentes ...
Michael Shaw
1
Cobertura del código! = Probado - aparte de los errores que se cancelan entre sí, ¿qué pasa con los escenarios en los que nunca has pensado? Las pruebas de integración están bien para las pruebas de ruta feliz, pero rara vez veo pruebas de integración adecuadas cuando las cosas no van bien.
Michael
44
@Ptolomeo Creo que la rareza de que 2 componentes defectuosos se cancelen entre sí es muy, muy inferior a 2 componentes que interfieren entre sí.
gbjbaanb
2
@Michael, entonces no has puesto suficiente esfuerzo en las pruebas, dije que es más difícil hacer una buena prueba de integración ya que las pruebas tienen que ser mucho más detalladas. Puede proporcionar datos incorrectos en una prueba de integración tan fácilmente como puede hacerlo en una prueba unitaria. Pruebas de integración! = Camino feliz. Se trata de ejercer la mayor cantidad de código posible, es por eso que hay herramientas de cobertura de código que le muestran cuánto de su código se ha ejercido.
gbjbaanb
1
@ Michael Cuando uso herramientas como Cucumber o SpecFlow correctamente, puedo crear pruebas de integración que también prueban excepciones y situaciones extremas tan rápido como las pruebas unitarias. Pero estoy de acuerdo si una clase tiene muchas permutaciones. Prefiero escribir una prueba unitaria para esta clase. Pero esto será menos frecuente que tener clases con solo un puñado de caminos.
Yggdrasil
2

Creo que es una idea horrible.

Dado que las pruebas de aceptación y la prueba de integración tocan partes más amplias de su código para probar un objetivo específico, necesitarán más refactorización con el tiempo, no menos. Peor aún, dado que cubren secciones amplias del código, aumentan el tiempo que pasa rastreando la causa raíz ya que tiene un área más amplia para buscar.

No, por lo general, debe escribir más pruebas unitarias a menos que tenga una aplicación impar que tenga una interfaz de usuario del 90% o algo más que sea incómodo para la prueba unitaria. El dolor con el que te encuentras no proviene de las pruebas unitarias, sino de hacer el primer desarrollo de la prueba. En general, solo debe pasar 1/3 de su tiempo en la mayoría de las pruebas de escritura. Después de todo, están ahí para servirle, no al revés.

Telastyn
fuente
2
La principal queja que escucho contra TDD es que interrumpe el flujo de desarrollo natural y hace cumplir la programación defensiva desde el principio. Si un programador ya está bajo presión de tiempo, es probable que desee cortar el código y pulirlo más tarde. Por supuesto, cumplir una fecha límite arbitraria con código defectuoso es una economía falsa.
Robbie Dee
2
De hecho, especialmente porque "pulirlo más tarde" nunca parece suceder realmente: cada revisión que hago donde un desarrollador presiona "necesita salir, lo haremos más tarde" cuando todos sabemos, eso no va a suceder - técnico deuda = desarrolladores en quiebra en mi opinión.
Michael
3
La respuesta tiene sentido para mí, no sé por qué tuvo tantos inconvenientes. Para citar a Mike Cohn: "Las pruebas unitarias deberían ser la base de una estrategia sólida de automatización de pruebas y, como tal, representan la parte más grande de la pirámide. Las pruebas unitarias automatizadas son maravillosas porque dan datos específicos a un programador: hay un error y está encendido línea 47 " mountaingoatsoftware.com/blog/…
guillaume31
44
@ guillaume31 El hecho de que un tipo haya dicho una vez que son buenos no significa que sean buenos. En mi experiencia, los errores NO son detectados por las pruebas unitarias, porque se cambiaron al nuevo requisito por adelantado. También la mayoría de los errores son errores de integración. Por lo tanto, detecto la mayoría de ellos con mis pruebas de integración.
Yggdrasil
2
@Yggdrasil También podría citar a Martin Fowler: "Si obtiene una falla en una prueba de alto nivel, no solo tiene un error en su código funcional, también le falta una prueba unitaria". martinfowler.com/bliki/TestPyramid.html De todos modos, si las pruebas de integración solo funcionan para usted, entonces está bien. Mi experiencia es que, aunque son necesarios, son más lentos, entregan mensajes de falla menos precisos y menos maniobrables (más combinatorios) que las pruebas unitarias. Además, tiendo a estar menos preparado para el futuro cuando escribo pruebas de integración, razonando sobre escenarios concebidos antes (¿mis?) En lugar de la corrección de un objeto per se.
guillaume31
2

El "triunfo" con TDD es que una vez que las pruebas se han escrito, pueden automatizarse. La otra cara es que puede consumir una parte significativa del tiempo de desarrollo. Si esto realmente ralentiza todo el proceso es discutible. El argumento es que las pruebas iniciales reducen el número de errores que se corregirán al final del ciclo de desarrollo.

Aquí es donde entra BDD, ya que los comportamientos se pueden incluir dentro de las pruebas unitarias, por lo que el proceso es, por definición, menos abstracto y más tangible.

Claramente, si hubiera disponible una cantidad de tiempo infinita, haría tantas pruebas de diversas variedades como fuera posible. Sin embargo, el tiempo es generalmente limitado y las pruebas continuas solo son rentables hasta cierto punto.

Todo esto lleva a la conclusión de que las pruebas que proporcionan el mayor valor deben estar al frente del proceso. Esto en sí mismo no favorece automáticamente un tipo de prueba sobre otro, más que cada caso debe tomarse según sus méritos.

Si está escribiendo un widget de línea de comando para uso personal, le interesarían principalmente las pruebas unitarias. Mientras que un servicio web, por ejemplo, requeriría una cantidad sustancial de pruebas de integración / comportamiento.

Si bien la mayoría de los tipos de pruebas se concentran en lo que podría llamarse la "línea de carreras", es decir, probar lo que requiere el negocio hoy en día, las pruebas unitarias son excelentes para eliminar errores sutiles que podrían surgir en las fases de desarrollo posteriores. Dado que este es un beneficio que no se puede medir fácilmente, a menudo se pasa por alto.

Robbie Dee
fuente
1
Escribo mis pruebas por adelantado y escribo suficientes pruebas para cubrir la mayoría de los errores. En cuanto a los errores que aparecen más tarde. Esto me parece que un requisito ha cambiado o entra en juego un nuevo requisito. Que las pruebas de integración / comportamiento necesitan ser cambiadas, agregadas. Si luego se muestra un error en un requisito anterior, mi prueba para esto lo mostrará. En cuanto a la automatización. Todas mis pruebas se ejecutan todo el tiempo.
Yggdrasil
El ejemplo en el que estaba pensando en el último párrafo es donde, por ejemplo, una biblioteca fue utilizada exclusivamente por una sola aplicación, pero luego hubo un requisito comercial para hacer de esta una biblioteca de propósito general. En este caso, es mejor que tenga al menos algunas pruebas unitarias en lugar de escribir nuevas pruebas de integración / comportamiento para cada sistema que adjunte a la biblioteca.
Robbie Dee
2
Las pruebas automáticas y las pruebas unitarias son una materia completamente ortogonal. Cualquier proyecto que se respete tendrá una integración automatizada y pruebas funcionales. Por supuesto, no suele ver las pruebas unitarias manuales, pero pueden existir (básicamente, la prueba unitaria manual es una utilidad de prueba para algunas funciones específicas).
Jan Hudec
Pues de hecho. Ha habido un mercado floreciente para herramientas automatizadas de terceros que existen fuera del ámbito del desarrollo durante un tiempo considerable.
Robbie Dee
1

La última ventaja de las pruebas unitarias es que son más rápidas. He escrito suficientes pruebas de integración para saber que pueden ser casi tan rápidas como las pruebas unitarias.

Este es el punto clave, y no solo "la última ventaja". Cuando el proyecto se hace más y más grande, sus pruebas de aceptación de integración son cada vez más lentas. Y aquí, quiero decir tan lento que vas a dejar de ejecutarlos.

Por supuesto, las pruebas unitarias también se están volviendo más lentas, pero aún son más rápidas que el orden de magnitud. Por ejemplo, en mi proyecto anterior (c ++, unos 600 kLOC, 4000 pruebas unitarias y 200 pruebas de integración), me llevó aproximadamente un minuto ejecutar todas y más de 15 para ejecutar las pruebas de integración. Para construir y ejecutar pruebas unitarias para la parte que se está cambiando, tomaría menos de 30 segundos en promedio. Cuando puedas hacerlo tan rápido, querrás hacerlo todo el tiempo.

Solo para dejarlo claro: no digo que no agregue pruebas de integración y aceptación, pero parece que lo hizo TDD / BDD de manera incorrecta.

Las pruebas unitarias también son buenas para el diseño.

Sí, diseñar teniendo en cuenta la capacidad de prueba mejorará el diseño.

El problema era que más de la mitad del tiempo de desarrollo de las pruebas de escritura / refactorización. Así que al final no quería implementar más funciones porque necesitaría refactorizar y escribir en muchas pruebas.

Bueno, cuando los requisitos cambian, tienes que cambiar el código. Te diría que no terminaste tu trabajo si no escribiste pruebas unitarias. Pero esto no significa que deba tener una cobertura del 100% con las pruebas unitarias; ese no es el objetivo. Algunas cosas (como GUI, o acceder a un archivo, ...) ni siquiera están destinadas a ser probadas por la unidad.

El resultado de esto es una mejor calidad de código y otra capa de prueba. Yo diría que vale la pena.


También tuvimos varias pruebas de aceptación de miles, y tomaría toda una semana ejecutarlas todas.

BЈовић
fuente
1
¿Has mirado mi ejemplo? Esto sucedió todo el tiempo. El punto es que, cuando implemento una nueva característica, cambio / agrego la prueba unitaria para que prueben la nueva característica, por lo tanto, no se interrumpirá ninguna prueba unitaria. En la mayoría de los casos, tengo efectos secundarios de los cambios que no son detectados por las pruebas unitarias, porque el entorno es burlado. En mi experiencia debido a esto, ninguna prueba de unidad me dijo que había roto una característica existente. Siempre fueron las pruebas de integración y aceptación las que me mostraron mis errores.
Yggdrasil
En cuanto al tiempo de ejecución. Con una aplicación creciente, tengo principalmente un número creciente de componentes aislados. Si no, he hecho algo mal. Cuando implemento una nueva característica, es principalmente solo en un número limitado de componentes. Escribo una o más pruebas de aceptación en el alcance de toda la aplicación, lo que podría ser lento. Además, escribo las mismas pruebas desde el punto de vista de los componentes: estas pruebas son rápidas, porque los componentes suelen ser rápidos. Puedo ejecutar las pruebas de componentes todo el tiempo, porque son lo suficientemente rápidas.
Yggdrasil
@Yggdrasil Como dije, las pruebas unitarias no son todas poderosas, pero generalmente son la primera capa de pruebas, ya que son las más rápidas. Otras pruebas también son útiles y deben combinarse.
B 17овић
1
El hecho de que sean más rápidos no significa que deban usarse solo por esto o porque es común escribirlos. Como dije, mis pruebas unitarias no se rompen, por lo que no tienen ningún valor para mí.
Yggdrasil
1
La pregunta era, ¿qué valor tengo de la prueba unitaria, cuando no se rompen? ¿Por qué molestarse en escribirlos, cuando siempre necesito ajustarlos a los nuevos requisitos? El único valor que veo de ellos es para algoritmos y otras clases con una alta permutación. Pero estos son menos que el componente y las pruebas de aceptación.
Yggdrasil