¿Vale la pena crear pruebas unitarias?

La creación de pruebas unitarias requiere al menos lo siguiente:

  • Un framework de pruebas unitarias que debemos dominar. Por lo general son muy simples.
  • Código testeable. Típicamente esto implica código susceptible de ser “aislado”.

Nada que decir con respecto al primer punto. Ahora, en cuanto al segundo punto, ¿qué significa que el código pueda ser “aislado”?. Bueno, esto significa que sus dependencias deben poder ser reemplazadas. Esto lo logramos bien por medio de inyectarle sus dependencias o bien utilizando un patrón ServiceLocator (a algún framework con un contenedor de dependencias).

Entonces, una vez resuelto, necesitamos proveer al código objeto de prueba, dependencias “falsas”. Esto nuevamente requiere o bien que las creemos a manos o que  utilicemos algún isolation framework para crearlas (por lo general en runtime).

Hasta ahora nada que no sepamos todos ¿verdad? Pero… ¡Vamos de nuevo! Veámoslo en código por favor. Imaginemos que tenemos el siguiente método pero no podemos probarlo porque su dependencia con el LogHelper (una clase con métodos estáticos) nos lo impiden:

public class TransferManager

{

    public void Tranfer(Account from, Account to, Money amount)

    {

        // Perform the required actions

        LogHelper.Inform("Done!");

    }

}

Si no quisiéramos enredarnos mucho, lo que haríamos sería algo como lo que sigue:

public class TransferManager

{

    private readonly ILogger _logger;

 

    public TransferManager(ILogger logger)

    {

        this._logger = logger;

    }

 

    public void Tranfer(Account from, Account to, Money amount)

    {

        // Perform the required actions

        _logger.Inform("Done!");

    }

}

Y la prueba, se parecería a lo que sigue:

[TestMethod]

public void Perform_a_valid_tranfer()

{

    var aFakeLogger = new FakeLogger();

    var tranferManager = new TransferManager(aFakeLogger);

 

    // Do and Assert here

}

Lo que falta:

interface ILogger

{

    void Inform(string messageToLog);

}

 

class FakeLogger : ILogger

{

    public void  Inform(string messageToLog)

    {

        // Nothing to do

    }

}

¿Que debimos hacer para probar esa pieza de código? Bueno, varias cosas: en primer lugar fue necesario crear una interface hasta el momento innecesaria (ILogger), tuvimos que agregar un constructor para inyectarle esta dependencia juntamente con un campo para almacenarla; debimos además, y aunque no se muestre aquí, hacer un wrapper para el LogHelper que implemente la interface ILogger. Por otro lado, en el proyecto de prueba, debimos crear una clase falsa (la FakeLogger).

Creamos FakeLogger de manera manual y con su implementación más sencilla (no hace absolutamente nada) pero bien podríamos haberla creado con un framework de aislamiento y especificarle algún comportamiento.

Es decir que el código es ahora más complejo que antes ya que tiene, al menos en este ejemplo, un campo más (_logger), un constructor más, una interface más (la ILogger) y una clase más (el wrapper del LogHelper). El código que crea una instancia de esta clase (TranferManager) también se ve afectado. Este es el costo que lleva asociado ineludiblemente el probar, mediante pruebas unitarias, cualquier código.

Claro, la pregunta es: ¿Valdrá la pena?

Sin categoría

19 thoughts on “¿Vale la pena crear pruebas unitarias?

  1. Con «Moles» ya no es necesario modificar tanto el código para probar. Te recomiendo que veas el «webcast» de Luis Ruiz Pavón:

    http://geeks.ms/blogs/lruiz/archive/2011/03/23/secondnug-testeando-lo-intesteable-con-el-framework-de-moles.aspx

    Todavía parece que no está disponible el vídeo del «webcast».

    «Moles» permite interceptar las llamadas a… CUALQUIER OBJETO, EVENTO, ETC y sustituirlo por tu código de prueba. Es espectacular.

  2. Como todo, vale aquello que agregue valor.

    Además de los cambios que comentás, el modificar el código para que se pueda aislar ejercita el conocimiento sobre el desarrollo de software. Con esto quiero decir que más allá de hacer o no UT, hay un factor extra, el de aprender. El ejemplo propuesto que representa una de las soluciones más rápidas a este tipo de problemas puede ser un total enigma para muchos que no poseen o que no han experimentado y pasado por la necesidad de hacerlo. Entonces, puede que el costo de hacer todo este trabajo no sea necesario para ciertas partes de nuestro producto, pero para poder saber eso es necesario primero saber lo que planteaste en el post.

    Con todo esto digo que de nada sirve no hacer todo este trabajo para quien no sabe hacerlo, siendo que al hacerlo aprenderá y elevará el nivel de sus conocimientos para luego poder optar por hacerlo o no. (Si, un poco complicado)

    Por lo tanto, creo que estas, más allá de los posibles controles que podamos obtener sobre nuestro código, este nos permite hacer mejor código, una vez que hemos aprendido a hacerlo y podemos optar por si ponemos o no esfuerzo sobre la creación de pruebas y refactorización de código.

  3. Pues para el código a probar que has puesto tu, claramente no. 😛

    Y cuando sí valdría la pena el código que comprueba debe ser tanto o más complejo que el código a probar, y terminas sin saber si la prueba es correcta porque o bien fallan ambas partes del código o bien lo que luego se ejecutará en la realidad nada tiene que ver con tus pruebas…

  4. Es cierto, moles es justamente para eso. Tu comentario me ha ayudado a ver algo: Quizás, y solo quizás, el problema no sea tanto de las pruebas sino de la inyección de dependencias (muchas veces se abusa de esto por culpa de las pruebas).

    Por otra parte, si lees mi entrada anterior vas a ver que no creo demasiado en estas cosas. Moles es espectacular siempre que lo muestran en un PDC, en la vida real no lo es tanto. Si ves el código de los assemblies que genera y si has usado Pex en serio por algún tiempo, vas a darte cuenta que Moles solo nace como un trabajo derivado de Pex (porque los otros framewroks, pongamos de ejemplo a RhinoMocks, generan los tipos en tiempo de ejecución y Pex no es capaz de analizarlos (instrumentarlos). Es por esa razón que Moles crea un assembly con tipos sin ninguna lógica, solo unos delegados para que programes las respuesta a las invocaciones.

    No creas tanto en estas cosas. Si son realmente lo que prometen ser el tiempo (muy corto por cierto) se encargará de hacernoslo saber, de lo contrario será tan solo otra herramienta que pase con mas ruido que gloria.

    Un abrazo y gracias.

  5. Hola,

    Desde el respeto te digo que creo que me he perdido algo si a estas alturas seguimos haciendonos este tipo de preguntas.
    ¿Son útiles las pruebas unitarias? ¿Son útiles los gestores de fuentes? ¿Y tener una build? ¿Integración continua?¿hacer buen código? ¿Usar un análisis de código?

    Considero que cualquiera que haya desarrollado código con y sin pruebas, cualquiera que haya mantenido código, cualquier que haya abierto y modificado algo que no ha hechoo, incluso que él creo…opinará que las pruebas unitarias vienen muy bien!!

    Refactorizar, generar código de mucha mejor calidad, detectar los errores tempranos…en resumen, mucha y mejor calidad y claro está, LA CALIDAD NO ES OPCIONAL.
    http://geeks.ms/blogs/rcorral/archive/2006/09/02/En-el-software_2C00_-la-calidad_2C00_-no-es-opcional.aspx

    Si el código que alguien desarrolla es tan costoso de testear, igual no deberías preguntarte si las pruebas unitarias merecen la pena, sino si los arquitectos y desarrolladores del proyecto son los adecuados. Un código testeable es un síntoma de buen código, uno poco testeable generaría mis dudas.

    Te animo a que eches un ojo a la serie de post que escribí sobre el tema, sobre todo aquella que habla de los beneficios de las mismas y de los falsos mitos que corren alrededor.
    http://geeks.ms/blogs/ilanda/archive/tags/Pruebas+unitarias/default.aspx

    No quiero alargarme mucho más sobre este tema porque mucha gente de geeks y no de geeks ya ha hablado sobre ello y sobre los beneficios que tiene usarlas, por lo que no creo que merezca la pena seguir dándole vueltas…eso sí, cada uno que haga lo que quiera en sus desarrollos, en los míos la calidad no es opcional y las pruebas unitarias tampoco.

    Un saludo!

  6. Te voy a contestar con la frase que titula el blog …
    «Sigo pensando todo de nuevo mil veces y todavia encuentro mejores maneras de hacer lo mismo»
    En los test- igual. Siempre encontraras una manera mejor de hacer las cosas.

  7. Hola Ibon, creo que un pensamiento esceptico es siempre deseable ya que ejercita tu capacidad de pensar. Por ejemplo, a tus preguntas ¿Son útiles los gestores de fuentes? ¿Y tener una build? ¿Integración continua?¿hacer buen código? ¿Usar un análisis de código? Mis respuestas son si si, si, si y si. En cuanto a la primera «¿Son útiles las pruebas unitarias?» mi respuesta es: algunas veces si. Me permito dudarlo de vez en cuando.

    Por supuesto que refactorizar y generar código de mejor calidad es bueno, a lo que voy es que el primer ejemplo tiene seguramente la misma (quizás mejor todavia) calidad que el que resultó después de refactorizarlo para probarlo.

    Nuevamente estoy de acuerdo contigo en tu 4 parrafo, un código testeable es quizás un síntoma de buen código. La pregunta es si un código duro de testear es necesariamente un sintoma de mal código.

    ¿Te has dado cuenta que todos los beneficios que se le asignan a las pruebas están por lo general exagerados? Por lo general se dicen cosas como que son el mejor lugar para entender como funciona una pieza de código (o que sirven de documentación), bueno, la verdad es que eso es más retórica que verdad porque las pruebas son solo un cliente más del código que prueban así que por lo general los desarrolladores no van tan seguido a las pruebas para ver esa «documentación». También habrás oido que depuras menos o que alienta el compartir código porque te da confianza de que no rompiste nada. Bueno, sí, es posible. En cuanto a lo que decis sobre la detección temprana de fallos… bueno, esa es la idea ¿o no?. ¿Pero cual es el costo de esa detección más temprana? porque de costos se trata. Desde mi punto de vista los costos y beneficios de las pruebas unitarias no están debidamente probados (lo que no significa que no sirvan, ni mucho menos). Los resultados de los estudios que se han realizado son tan disímiles y tienen un rango tan amplio que uno no sabe si aportan muchísimo, mucho, algo, poco o nada.
    Por esa razón es que me gusta pensar, cuestionar y volver a pensar… paso del amor al odio y del odio al amor muy rápido con este tema. En el ejemplo que puse en esta entrada por ejemplo, y quitando por un momento a Moles de esta discusión, el código no mejoró en calidad, no es más entendible ni más simple, quizás es todo lo contrario. ¿No hemos acaso incurrido en una pequeña sobreingeniería con el único afán de escribir pruebas?

    Creeme que sería muy facil para mi el subirme al caballo de lo que opinan todos y gozar de cierto aprecio y respeto por la comunidad sombie. Pero no, esta es mi vereda. Si algo me huele extraño lo voy a poner en dudas, siempre.

    Un saludo.

  8. Yo pretendía quedarme con una sola respuesta a tu pregunta… un SI gigante… pero veo que aún lo sigues dudando después de los comentarios. 🙂 Las dudas son la base del desarrollo, así que no te lo critico…

    Los beneficios no son para nada exagerados.. TDD es el desarrollo orientado a pruebas, imagina escribir esto:

    void YoQuieroAgua_Test()
    {
    var yo = new Yo();
    var vaso = yo.QuieroAgua(Vaso miVaso);
    Assert.IsTrue(vaso.Lleno && vaso.Contiene(Agua));

    //…
    }

    ¿En ese punto qué sabes de ese método? Pues que se quiere agua y que se tiene un vaso. ¿Necesitas más documentación para saber lo que se quiere? NO… lo que hagas a partir de ahí, puede quedar transparente a la documentación. El test sí documenta porque como dices, es un cliente más de nuestro código y toda documentación sale de cómo se usa algo y no cómo lo hace.. ¿Te imaginas la documentación de una tele 3D explicando cómo funciona internamente? ummm 🙂

    Costes!! ohh los costes… 😉 el problema aquí radica en lo de siempre, nos demoramos haciendo test!!.. pues sí.. ¿nos demoramos y qué? 🙂 En principio, y es realmente este el punto en que cuesta entender el test, ocupa tiempo de desarrollo y todos lo ven como un algo innecesario… pero el tiempo es imparable y pasa y pasa y pasa…

    Llega el momento en que estás desarrollando el 10mo módulo de tu proyecto ¿Quién te garantiza que los cambios que hagas en ese módulo no afectarán el primero (que das ya por listo y cerrado)? Síii.. 🙂 Respuesta correcta: El test!!

    Si no tienes un test o varios.. muchos… cientos de test que prueben todo tu módulo 1.. jamás podrás garantizar que el mismo sigue funcionando como esperas.. ¿y si lo garantizas que estás haciendo? pues disminuyendo los costes que podrían representar el dedicar tiempo a corregir problemas que han surgido en el módulo 1 al crear el 10…eso sin quitar que los arreglos rompan el módulo 2.. y el 2 el 8.. y el 8 cueste tu despido.. 🙂

    Serio, es bueno que lo dudes.. pero procura que esa duda te haga seguir profundizando en la importancia de los test unitarios y que 3, 4, 8, 10 meses después, nos regales un artículo que se titule: «La importancia de los test unitarios»

    Salu2

  9. El desarrollo es un producto…
    os imagináis algún producto que salga al mercado sin haber sido sometido a pruebas unitarias?
    Que tienen un coste está claro, hay que tener en cuenta siempre los costes de la calidad pero no nos olvidemos nunca de los costes de la NO CALIDAD…

  10. Todo lo que contáis está muy bien a nivel técnico. A mí personalmente me encantan las pruebas unitarias (no para todo). Pero os cuento la situación que tengo ahora entre manos.
    Me han pedido que valore una migración de una aplicación de facturación. La cual está hecha original mente en Delphi, y tienen unas tareas automáticas sobre .Net. Ahora quieren que vaya todo sobre .Net
    Estas tareas automáticas están sin documentar, y lo normal es que cada vez que intentan meter una nueva funcionalidad rompan tres. Así que cuando hice el presupuesto, además de la migración de Delphi a .net presupueste el implementar pruebas unitarias para estas tareas automáticas. Ya que luego nos va a tocas realizar el mantenimiento.
    Bueno, pues lo primero que hizo el cliente es echarse las manos a la cabeza. Se reunió el gerente del cliente con el de mi empresa. Han quitado todos los conceptos de pruebas unitarias. Sobre el resto lo han reducido 20% en tiempos. Y ahora nos dicen que hay otra empresa que se lo hace por la mitad.
    Sinceramente, si fuera yo el gerente, pasaría del proyecto. Porque esto al final no va a salir bien.
    El problema de todo esto siempre es el mismo. La calidad de código solo la sabe valorar quien se remanga y se pringa las manos. Pero en cuanto subes de jefe de proyecto, la única métrica que se conoce, son las horas que pasas sentado en la silla.

  11. Viendo el titulo del post, ya me he imaginado que tendrias bastantes comentarios.
    Por lo que parece hay mucha gente que se le llena la boca al hablar de calidad, que si pruebas unitarias etc… cuando la realidad es la siguiente: el 5% de los proyectos tienen pruebas unitarias, el 15% tienen esqueletos de lo que en su dia fueron pruebas unitarias pero que por razones de tiempo y costes se han dejado en el olvido, y el 80% restante nisiquiera contempla nada de test unitarios.

    Y sinó, que cada uno haga un pequeño calculo de todos los proyectos en los que ha participado. Cuantos no contemplaban pruebas unitarias? y cuantos contemplaron pruebas unitarias hasta el final?. Y de estos ultimos, lo mas importante, fueron utiles las pruebas unitarias?

    Que conste que estoy totalmente a favor de los tests unitarios, test funcionales etc.. cuantos mas mejor. Pero hay que ser mas critico con lo que se da por hecho.

  12. El señor Landa exagera un poco con lo de la arquitectura estupenda si se puede probar con facilidad. En muchos casos ampliar la facilidad de realizar pruebas, es decir, aumentar los «stubs», incrementa de forma innecesaria la complejidad y reduce el rendimiento.

    El camino adecuado es el marcado por «Moles»: interceptar llamadas y sustituir por código de pruebas.

  13. yo nunca he visto pruebas unitarias que valgan la pena… al final se pierde más tiempo haciéndolas que desarrollando la aplicación, y tampoco les veo mucha utilidad a unas pruebas unitarias en una conjutura de aplicación empresarial con una lógica compleja, en la que deben intervenir distintas funciones.

    Se que estan muy «de moda» y que es ir contra corriente afirmar eso, pero así es. Hacer pruebas unitarias es perder tiempo y dinero. Eso sí, es muy bonito decirle al cliente que se hacen todo tipo de pruebas… y siempre queda bien (a mi no me lo parece pero bueno).

  14. Buenas,
    No voy a entrar a discutir sobre la utilidad de las pruebas unitarias, pero quiero comentar algo que me ha sorprendido un poco 🙂

    @Lucas, parte de tu razonamiento viene de que partes de la hipótesis de que crear este código que llamas testeable requiere trabajo. Lo que me sorprende es que eso da a la idea de que sólo es necesario hacer esto que comentas (básicamente, desacoplar) con el ÚNICO objetivo de realizar pruebas. Y eso es totalmente falso y un contrasentido además.

    El código desacoplado ofrece muchas ventajas sobre el que no lo es, y que sea «testeable» es solo una más. Pero desacoplar el código debería ser una práctica común y habitual en todos nosotros. Y pienso que @Ibon se refiere a eso cuando dice que un código no testeable le genera dudas. Un código bien realizado, y eso significa (entre otras cosas) desacoplado, será testeable con mucha más facilidad.

    En fin, resumiendo: que NO DEBERIA SER NECESARIO modificar el código para probarlo.

    Un saludo 😉

  15. Creo que nadie tiene razón ni se equivoca cuando debate sobre estas cosas, y mucho menos cuando uno no tiene contexto suficiente para hacer ese tradeoff.

    @Eduard, en este ejemplo particular no es necesario modificar el código de la manera en que lo hice para probarlo… bastaría tal vez con agragarle un método «Disable()» al logguer y listo, verdad? El tema es que, si bien parece que he sido injusto con las pruebas, desacoplarse de algo debería también ser objeto de un trade off.

    @Marcos, tu punto es muy bueno. Eso es justamente lo que pregunto acerca de las pruebas: ¿es el costo de calidad mayor que el de no calidad?. Ahora……. no hace falta tener mucha imaginación para pensar en producto sin pruebas unitarias en el mercado. Sinceramente, y sin tener números en la mano, creo que la inmensa mayoría de los productos que se encuentran en el mercado no tienen pruebas unitarias.

    @Omar, no quiero cambiar el foco hacia TDD. Las pruebas no son, al menos para mi, buena documentación, el ejemplo que pusiste podemos verlo en el código y de la misma manera (o muy parecida). Pero el tema no es saber si son útiles o no, el punto es pensar en si la inversión se justifica plenamente o en que grado. Probablemente habrá casos y casos. No soy un anti-UTs, solo quiero ver las cosas con mis propios ojos y no copiar y pegar desde cabezas ajenas.

  16. En mi opinion, las pruebas unitarias son fundamentales. El desarrollo del software para mi hay un antes y un despues con respecto a este tema.
    El asunto es que hacer pruebas unitarias no necesariamente significa usar mocking e inyeccion de depedencias. Este es el problema que existe hoy en dia con los test unitarios. Se abusa de estos patrones, frameworks y al final, termina uno compliando innecesariamente lo que deberia ser simple.
    Crear test unitarios (o si queres, test de integracion) para mi es muy necesario. Usar mocking no.
    Yo cambiaria el titulo de tu post. Vale la pena usar mocking?

  17. @Lucas: tu pregunta es:
    ¿es el costo de calidad mayor que el de no calidad?
    para mí la respuesta más valida surge de estas dos preguntas… ¿se pueden estimar los costes de calidad?…SI, ¿se pueden estimar los costes de no calidad?… NO
    ¿Que prefieres para tu proyecto…? ¿Te gusta el riesgo…?

Responder a rfog Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *