¿Cuándo es apropiado no realizar una prueba unitaria?

129

Trabajo en una pequeña empresa como desarrollador en solitario. De hecho, soy el único desarrollador de la empresa. Tengo varios proyectos (relativamente) grandes que he escrito y mantenido con regularidad, y ninguno de ellos tiene pruebas que los respalden. Al comenzar nuevos proyectos, a menudo me pregunto si debería probar un enfoque TDD. Parece una buena idea, pero honestamente nunca puedo justificar el trabajo adicional involucrado.

Trabajo duro para ser progresivo en mi diseño. Me doy cuenta de que, un día, otro desarrollador tendrá que mantener mi código, o al menos solucionarlo. Mantengo las cosas lo más simples posible y comento y documento cosas que serían difíciles de entender. Y el hecho es que estos proyectos no son tan grandes o complicados que un desarrollador decente tendría dificultades para comprenderlos.

Muchos de los ejemplos que he visto de las pruebas se remontan a los detalles, que cubren todas las facetas del código. Como soy el único desarrollador y estoy muy cerca del código en todo el proyecto, es mucho más eficiente seguir un patrón de prueba de escritura y luego de forma manual. También encuentro que los requisitos y las características cambian con la frecuencia suficiente para que el mantenimiento de las pruebas agregue una cantidad considerable de resistencia al proyecto. Tiempo que de otra manera podría dedicarse a resolver las necesidades del negocio.

Así que termino con la misma conclusión cada vez. El retorno de la inversión es demasiado bajo.

Ocasionalmente he realizado algunas pruebas para garantizar que he escrito un algoritmo correctamente, como calcular el número de años que alguien ha estado en la empresa según la fecha de contratación. Pero desde el punto de vista de la cobertura de código, he cubierto aproximadamente el 1% de mi código.

En mi situación, ¿todavía encontraría una manera de hacer que la prueba de unidad sea una práctica regular, o estoy justificado para evitar esa sobrecarga?

ACTUALIZACIÓN: algunas cosas sobre mi situación que omití: todos mis proyectos son aplicaciones web. Para cubrir todo mi código, tendría que usar pruebas automatizadas de UI, y esa es un área en la que todavía no veo un gran beneficio sobre las pruebas manuales.

    
pregunta Ken Pespisa 08.04.2011 - 17:41

14 respuestas

78
  

Muchos de los ejemplos que he visto de las pruebas se remontan a los detalles, que cubren todas las facetas del código.

Entonces? No tienes que probar todo . Solo las cosas relevantes.

  

Ya que soy el único desarrollador y estoy muy cerca del código en todo el proyecto, es mucho más eficiente seguir un patrón de prueba de escritura y luego de forma manual.

Eso es realmente falso. No es más eficiente. Es solo un hábito.

Lo que hacen otros desarrolladores en solitario es escribir un boceto o esquema, escribir los casos de prueba y luego completar el esquema con el código final.

Eso es muy, muy eficiente.

  

También encuentro que los requisitos y las características cambian con la frecuencia suficiente para que el mantenimiento de las pruebas agregue una cantidad considerable de resistencia al proyecto.

Eso también es falso. Las pruebas no son el arrastre. Los cambios de requisitos son el arrastre.

Tienes que arreglar las pruebas para reflejar los requisitos. Si sus minucias, o de alto nivel; escrito primero o escrito último.

El código no se hace hasta que las pruebas pasan. Esa es la única verdad universal del software.

Puedes tener una prueba de aceptación limitada "aquí está".

O puedes tener algunas pruebas unitarias.

O puedes tener ambos.

Pero no importa lo que hagas, siempre hay una prueba para demostrar que el software funciona.

Sugeriría que un poco de formalidad y un buen conjunto de herramientas de prueba de unidad hace que la prueba sea mucho más útil.

    
respondido por el S.Lott 08.04.2011 - 18:06
101

Imagina que tienes un conjunto de pruebas que podrían ejecutarse en un abrir y cerrar de ojos e iluminar una luz verde o roja. ¡Imagina que este conjunto de pruebas ha probado todo ! Imagine que todo lo que tenía que hacer para ejecutar el conjunto de pruebas era escribir ^ T. ¿Qué poder te daría esto?

¿Podrías hacer un cambio en el código sin temor a romper algo? ¿Podría agregar una nueva característica sin temor a romper una característica anterior? ¿Podrías limpiar el código desordenado rápidamente sin temor a causar daños?

¡Sí, podrías hacer todas esas cosas! ¿Y qué pasaría con tu código con el tiempo? Se limpiaría más y más porque no habría riesgo de limpiarlo.

Imaginemos que tuviste una pequeña hada en tu hombro. Cada vez que escribía una línea de código, el hada agregaría algo a la suite de prueba que probaba que esa línea de código hacía lo que estaba destinado a hacer. Por lo tanto, cada par de segundos puede presionar ^ T y ver que la última línea de código que escribió funcionó.

¿Cuánta depuración crees que harías?

Si esto suena a fantasía, tienes razón. Pero la realidad no es muy diferente. Reemplaza el pestillo con unos segundos, y el hada con la disciplina TDD, y ya lo tienes.

Digamos que está volviendo a un sistema que construyó hace un año y que ha olvidado cómo crear uno de los objetos centrales. Hay pruebas que crean ese objeto en todas las formas en que se puede crear. Puedes leer esas pruebas y refrescar tu memoria. ¿Necesitas llamar a una API? Hay pruebas que llaman a esa API de todas las formas en que se puede llamar. Estas pruebas son pequeños documentos , escritos en un idioma que usted entiende. Son completamente inequívocos. Son tan formales que lo ejecutan. ¡Y no pueden desincronizarse con la aplicación!

¿No vale la pena la inversión? ¡Tienes que estar bromeando! ¿Cómo podría alguien NO querer ese conjunto de pruebas? Hazte un favor y deja de regatear por tonterías. Aprende a hacer TDD bien, y observa cuánto más rápido vas y cuánto más limpio es tu código.

    
respondido por el Uncle Bob. 09.04.2011 - 01:04
32

El error que está cometiendo es ver que las pruebas son una inversión de tiempo sin retorno inmediato. No necesariamente funciona así.

Primero, escribir pruebas realmente lo enfoca en lo que esta parte de su código debe hacer.

En segundo lugar, ejecutarlos revela errores que de lo contrario surgirían en las pruebas.

En tercer lugar, ejecutarlos a veces muestra errores que de otra manera no aparecerían en las pruebas y luego realmente te morderían el culo en producción.

En cuarto lugar, si golpea un error con un sistema que se está ejecutando y crea una prueba unitaria para él, no podrá volver a introducir ese error más adelante. Eso puede ser una gran ayuda. Los errores reintroducidos son comunes y muy molestos.

En quinto lugar, si alguna vez necesita entregar el código a otra persona, un conjunto de pruebas les facilitará la vida. Además, si ha ignorado un proyecto y vuelve a él después de algunos años, ya no estará tan cerca de él y será útil para usted también.

Mi experiencia siempre ha sido que a lo largo del desarrollo de un proyecto, tener pruebas de unidades decentes siempre ha hecho que el proceso sea más rápido y más confiable.

    
respondido por el glenatron 08.04.2011 - 17:58
31

Los chicos de JUnit (Java Unit test framework) tienen la filosofía de que si es demasiado simple de probar, no lo pruebes . Recomiendo leer sus Preguntas frecuentes sobre las mejores prácticas , ya que es bastante pragmático.

TDD es un proceso diferente para escribir su software. La premisa básica detrás de la prueba de unidad es que pasará menos tiempo en el paso del depurador a través del código y se dará cuenta más rápidamente si el cambio de código rompe accidentalmente algo más en el sistema. Eso encaja con TDD. El ciclo TDD es así:

  1. escribe una prueba
  2. Míralo fallar (demuestra que tienes algo que hacer)
  3. Escriba solo lo que se necesita para hacer que la prueba pase, no más.
  4. Míralo pasar (¡yay!)
  5. Refactor (hazlo mejor)
  6. Lavar, enjuagar y repetir

Lo que es menos obvio sobre la aplicación de TDD es que cambia la forma en que escribe su código . Al obligarse a pensar cómo probar / validar que el código está funcionando, usted está escribiendo código verificable. Y ya que estamos hablando de pruebas unitarias, eso generalmente significa que su código se vuelve más modular. Para mí, el código modular y comprobable es una gran victoria desde el principio.

Ahora, ¿necesitas probar cosas como las propiedades de C #? Imagina una propiedad definida como esta:

bool IsWorthTesting {get; set;}

La respuesta sería "no", no vale la pena probarlo, porque en este punto estás probando la función de idioma. Solo confía en que los chicos de la plataforma C # lo hicieron bien. Además, si falla, ¿qué podría hacer para solucionarlo?

Además, encontrará que hay ciertas partes de su código que muy bien serán demasiado esfuerzo para probarlas correctamente. Eso significa que no lo hagas, pero asegúrate de probar el código que usa / es usado por el problema complicado:

  • Excepciones comprobadas que solo pueden ocurrir si una instalación se ha dañado. Java tiene un montón de estos. Se requiere que escriba un bloque catch o declare la excepción marcada, incluso si no hay forma de que falle sin hackear los archivos instalados.
  • Interfaces de usuario. Encontrar el control bajo prueba e invocar los eventos correctos para simular las acciones de un usuario es muy problemático y, en algunos casos, imposible. Sin embargo, si usa el patrón Modelo / Vista / Controlador, puede asegurarse de que su modelo y controladores estén probados y dejar la parte de la vista a prueba manual.
  • Interacciones cliente / servidor. Esto ya no es una prueba de unidad, y ahora es una prueba de integración . Escriba todas las partes relacionadas con el envío y la recepción de mensajes a través del cable, pero en realidad no lo haga. Un buen enfoque es reducir la responsabilidad del código que realmente habla por cable a las comunicaciones en bruto. En el código de prueba de su unidad, simule un objeto de comunicación para asegurarse de que los servicios se comporten como espera.

Lo creas o no, TDD te ayudará a alcanzar un ritmo de desarrollo sostenible. No se debe a la magia, sino más bien porque tienes un circuito de retroalimentación ajustado y eres capaz de atrapar errores realmente tontos rápidamente. El costo de corregir esos errores es esencialmente constante (al menos lo suficiente para propósitos de planificación) porque los pequeños errores nunca llegan a ser grandes errores. Compare eso con la naturaleza de ráfaga de código de borrachera / depuración de purgas de purga.

    
respondido por el Berin Loritsch 08.04.2011 - 18:44
23

Debe equilibrar el costo de las pruebas con el costo de los errores.

Escribir una prueba de unidad de 10 líneas para una función que abre un archivo, donde la falla es "no se encontró el archivo" no tiene sentido.

Una función que hace algo complejo a una estructura de datos compleja, obviamente, sí.

El bit complicado está en el medio. Pero recuerde que el valor real de las pruebas unitarias no es probar la función en particular, sino probar las interacciones difíciles entre ellas. Por lo tanto, una prueba de unidad que detecta que un cambio en un bit de código, rompe alguna función en un módulo diferente a 1000 líneas de distancia, vale su peso en el café.

    
respondido por el Martin Beckett 08.04.2011 - 17:54
22

Las pruebas son juegos de azar.

Crear una prueba es una apuesta a que el costo de los errores en una unidad que se produce y no detectarlos con esa prueba (ahora, y durante todas las revisiones futuras del código) es mayor que el costo de desarrollar la prueba. Esos costos de desarrollo de pruebas incluyen cosas como la nómina para la ingeniería de pruebas agregada, el tiempo de lanzamiento al mercado agregado, los costos de oportunidad perdidos por no codificar otras cosas, y etc.

Como en cualquier apuesta, a veces ganas, a veces pierdes.

En algún momento, un software tardío con muchos menos errores gana sobre las cosas rápidas pero con errores que llegan al mercado primero. A veces lo contrario. Debe mirar las estadísticas en su campo en particular y cuánto quiere la administración apostar.

Algunos tipos de errores pueden ser tan poco probables de hacer, o de salir de cualquier prueba de sanidad temprana, como estadísticamente no vale la pena crear pruebas específicas adicionales. Pero a veces el costo de un error es tan grande (médico, nuclear, etc.) que una empresa debe apostar una pérdida (similar a la compra de un seguro). Muchas aplicaciones no tienen un costo de falla tan alto, y por lo tanto no necesitan la mayor cobertura de seguro antieconómico. Otros lo hacen.

    
respondido por el hotpaw2 26.04.2011 - 21:30
10

Mi consejo es que solo pruebes el código que quieres que funcione correctamente.

No pruebe el código que desea que tenga errores y que le cause problemas en el futuro.

    
respondido por el Nick Hodges 09.04.2011 - 07:45
8
  

A menudo me pregunto si debería probar un enfoque TDD. Parece una buena idea, pero honestamente nunca puedo justificar el trabajo adicional involucrado.

TDD y Unit Testing no son lo mismo.

Puedes escribir código y luego agregar pruebas unitarias más tarde. Eso no es TDD, y es mucho trabajo extra.

TDD es la práctica de codificar en un bucle de Luz Roja. Luz verde. Iteraciones de refactor.

Esto significa escribir pruebas para el código que aún no existe, ver cómo fallan las pruebas, corregir el código para que las pruebas funcionen y luego hacer que el código sea "correcto". Esto a menudo te ahorra trabajo

Una de las ventajas de TDD es que reduce la necesidad de pensar en trivialidades. Las cosas como los errores off-by-one desaparecen. No tiene que buscar en la documentación de la API para averiguar si la lista que devuelve comienza en 0 o 1, simplemente hágalo.

    
respondido por el Paul Butcher 08.04.2011 - 18:08
3

Trabajé en un sistema donde probamos casi todo. Las ejecuciones notables a las pruebas fueron el código de salida PDF y XLS.

¿Por qué? Pudimos probar las partes que recopilaron los datos y construyeron el modelo que se usó para crear la salida. También pudimos probar las partes que descubrieron qué partes del modelo irían a los archivos PDF. No pudimos probar si el PDF se veía bien porque era totalmente subjetivo. No pudimos probar que todas las partes de un PDF eran legibles para un usuario típico porque eso también era subjetivo. O si la elección entre barras y gráficos circulares fue correcta para el conjunto de datos.

Si la salida va a ser subjetiva, hay pocas pruebas unitarias. Puedes hacer lo que valga la pena.

    
respondido por el sal 08.04.2011 - 22:39
2

Para muchas cosas, una 'prueba de escritura manual, luego' no toma más tiempo que escribir un par de pruebas. Los ahorros de tiempo provienen de la posibilidad de volver a ejecutar esas pruebas en cualquier momento.

Piense en esto: si tiene alguna cobertura de características decente con sus pruebas (no debe confundirse con la cobertura de códigos), y digamos que tiene 10 características: hacer clic en un botón significa que tiene aproximadamente 10 you are re-haciendo sus pruebas ... mientras se sienta y toma un sorbo de su café.

También no tienes tener para probar los minutos. Puede escribir pruebas de integración que cubran sus características si no desea llegar a los detalles específicos ... OMI, algunas pruebas unitarias son demasiado precisas para probar el idioma y la plataforma, y no el código.

TL; DR Realmente nunca es apropiado porque los beneficios son demasiado buenos.

    
respondido por el Steven Evers 08.04.2011 - 17:52
2

Dos respuestas muy buenas que he encontrado aquí:

  1. Cuándo realizar la prueba unitaria frente a la prueba manual
  2. Lo que no se debe probar cuando se trata de la Unidad ¿Pruebas?

Las justificaciones para evitar la sobrecarga percibida:

  • Ahorro inmediato de tiempo / costos para su empresa
  • Posible ahorro de tiempo / costo en la resolución de problemas / mantenimiento / extensión a largo plazo, incluso después de que se haya ido.

¿No querría dejar un gran producto de su lado como prueba de la calidad de su trabajo? Hablando en términos egoístas, ¿no es mejor para ti lo que haces?

    
respondido por el Aditya P 08.04.2011 - 17:53
1

Los desarrolladores profesionales escriben pruebas unitarias porque, a largo plazo, ahorran tiempo. Vas a probar tu código tarde o temprano, y si no lo haces, tus usuarios lo harán, y si tienes que corregir los errores más adelante, serán más difíciles de solucionar y tendrán más efectos en los efectos.

Si está escribiendo código sin pruebas y no tiene errores, entonces está bien. No creo que puedas escribir un sistema no trivial con cero errores, así que asumo que lo estás probando de una forma u otra.

Las pruebas unitarias también son cruciales para evitar regresiones cuando modifica o refactoriza el código anterior. No prueban su cambio no ha roto el código antiguo, pero le dan mucha confianza (siempre que pasen, por supuesto :))

No volvería a escribir un lote completo de pruebas para el código que ya ha enviado, pero la próxima vez que necesite modificar una función, sugeriría intentar escribir pruebas para ese módulo o clase, aumentar su cobertura al 70% + antes de aplicar cualquier cambio. A ver si te ayuda.

Si lo intentas y puedes decir honestamente que no fue una ayuda lo suficientemente justa, pero creo que hay suficiente evidencia de la industria de que ayudan a que al menos valga la pena probar el enfoque.

    
respondido por el Steve Haigh 08.04.2011 - 18:00
1

Parece que la mayoría de las respuestas son pro-TDD, aunque la pregunta no era sobre TDD sino sobre pruebas unitarias en general.

No hay una regla completamente objetiva detrás de qué prueba de unidad o no prueba de unidad. Pero hay un par de veces donde parece que muchos programadores no hacen pruebas de unidad:

  1. Métodos privados

Dependiendo de su filosofía OOP, puede crear métodos privados para separar las rutinas complejas de sus métodos públicos. Los métodos públicos generalmente están destinados a ser llamados en muchos lugares diferentes y se utilizan con frecuencia, y los métodos privados solo son llamados realmente por uno o dos métodos públicos en una clase o módulo para algo muy específico. Por lo general, es suficiente escribir pruebas unitarias para los métodos públicos, pero no los métodos privados subyacentes que hacen que algo de la magia suceda. Si algo sale mal con un método privado, sus pruebas de unidad de método público deberían ser lo suficientemente buenas para detectar estos problemas.

  1. Las cosas que ya sabes deberían funcionar (o las cosas probadas por otra persona)

Muchos nuevos programadores van en contra de esto cuando están aprendiendo a probar por primera vez, y piensan que necesitan probar cada línea que se está ejecutando. Si está utilizando una biblioteca externa y su funcionalidad está bien probada y documentada por sus autores, generalmente no tiene sentido probar la funcionalidad específica en las pruebas unitarias. Por ejemplo, alguien podría escribir una prueba para asegurarse de que su modelo ActiveRecord persiste en el valor correcto para un atributo con una devolución de llamada "antes_save" a la base de datos, aunque ese comportamiento en sí es ya probado a fondo en Rieles. El (los) método (s) que llama la devolución de llamada, quizás, pero no el comportamiento de la devolución de llamada en sí. Cualquier problema subyacente con las bibliotecas importadas se revelaría mejor mediante pruebas de aceptación, en lugar de pruebas unitarias.

Ambos podrían aplicarse independientemente de que estés haciendo TDD o no.

    
respondido por el Ravenstine 04.03.2016 - 23:53
0

Ken, Yo y muchos otros desarrolladores hemos llegado a la misma conclusión que ustedes varias veces a lo largo de nuestras carreras.

La verdad que creo que encontrará (como lo han hecho muchos otros) es que la inversión inicial en escribir pruebas para su aplicación puede parecer desalentadora, pero si están bien escritas y dirigidas a las partes correctas de su código, realmente pueden ahorra un montón de tiempo.

Mi gran problema fue con los marcos de prueba disponibles. Realmente nunca sentí que eran lo que estaba buscando, así que simplemente hice mi propia solución muy simple. Realmente me ayudó a acercarme al "lado oscuro" de las pruebas de regresión. Compartiré un pseudo fragmento básico de lo que hice aquí, y espero que puedas encontrar una solución que funcione para ti.

public interface ITest {
    public string Name {
        get;
    }
    public string Description {
        get;
    }
    public List<ITest> SubTests {
        get;
    }
    public TestResult Execute();
}

public class TestResult {
    public bool Succesful {
        get;
        set;
    }

    public string ResultMessage {
        get;
        set;
    }

    private Dictionary<ITest, TestResult> subTestResults = new Dictionary<ITest, TestResult>();
    public Dictionary<ITest, TestResult> SubTestResults {
        get {
            return subTestResults;
        }
        set {
            subTestResults = value;
        }
    }
}

La única parte difícil después de eso es averiguar qué nivel de granularidad crees que es el mejor "dinero por dinero" para cualquier proyecto que estés haciendo.

La creación de una libreta de direcciones obviamente requerirá menos pruebas que un motor de búsqueda empresarial, pero los fundamentos no cambian realmente.

¡Buena suerte!

    
respondido por el Adam Carstensen 08.04.2011 - 18:53

Lea otras preguntas en las etiquetas