TDD and Hard to Test Areas, Part1 / TDD las partes difíciles Parte 1

Traducción del artículo de Ian Cooper publicado el 7 de Julio de 2008. http://codebetter.com/iancooper/2008/07/07/tdd-and-hard-to-test-areas-part1/

Quería hablar sobre los problemas que las personas tienen cuando comienzan a trabajar con TDD, los mismos problemas que tienden a hacer que abandonen TDD después de un primer contacto. Esas son las áreas «difíciles de probar», las cosas que debe hacer el código de producción, que esas presentaciones y libros introductorios no parecen explicar bien. En esta publicación, comenzaremos con una revisión rápida de TDD y luego veremos por qué las personas fallan cuando comienzan a intentar usarlo. La próxima vez analizaremos más de cerca las soluciones.

Revisión

Clean Code

TDD es un enfoque de desarrollo en el que escribimos nuestro
test antes de escribir el código de producción. Los beneficios de esto son:

  • Ayuda a mejorar la calidad: los tests nos dan una respuesta rápida. Recibimos
    confirmación inmediata de que nuestro código se comporta como se esperaba. Indica dónde hay que arreglar un defecto.
  • Ayuda a pasar menos tiempo en el depurador. Cuando algo rompe nuestros tests
    a menudo son lo suficientemente granulares como para mostrarnos lo que ha salido mal, sin tener que depurar. Si no es así, probablemente no tengamos la granularidad de test suficiente o no están bien redactados. La depuración requiere tiempo, así que cualquier cosa que ayude a mantenernos fuera del depurador nos ayuda a entregar código con un coste menor.
  • Ayuda a producir código limpio: no agregamos funcionalidad especulativa, solo
    código para el que tenemos un test.
  • Ayuda a ofrecer un buen diseño: nuestro test no sólo muestra nuestro código, sino también
    diseño, porque el acto de escribir un test nos obliga a tomar decisiones sobre el diseño del SUT.
  • Ayuda a mantener un buen diseño: Nuestros tests nos permiten refactorizar, cambiando la
    implementación para eliminar los olores de código, al tiempo que confirma que nuestro código sigue funcionando correctamente. Esto nos permite hacer una re-arquitectura incremental, mantener el diseño simple y ajustado mientras agregamos nuevas funciones.
  • Ayuda a documentar nuestro sistema: si quieres saber cómo debe comportarse el SUT
    los test son un medio eficaz de comunicar esa información. Los tests proporcinan esos ejemplos.

Automatizar los tests reducen el coste de realizar estos tests. Pagamos el coste de hacer los tests una vez, pero luego podemos volver a ejecutar una y otra vez nuestros tests sin esfuerzo y eso nos ayuda a mantener el beneficio durante toda la vida útil del sistema. Automatizar los tests es «el regalo que sigue dando». El software pasa más tiempo de su vida en mantenimiento que en desarrollo, por lo que reducir el coste de mantenimiento reduce el coste del software.

Los pasos

Los pasos en TDD a menudo se describen como Red-Green-Refactor

  • Red (Rojo) : escriba una prueba fallida (no hay pruebas para pruebas, por lo que
    esto verifica su prueba por usted)
  • Green (Verde) : hazlo pasar, que no falle el test
  • Refactor: Resuelve los olores en la implementación del código que acabamos de agregar.

¿ Quieres profundizar más ?

El libro de Kent Beck Test-Driven Development, By Example es un clásico para aprender los conceptos básicos de TDD.

Definiciones rápidas


System Under Test (SUT): Independientemente de lo que estemos probando, esto puede diferir según el nivel del test. Para un test unitaria, esto podría ser una clase o método en esa clase. Para tests de aceptación, esto puede ser una parte de la aplicación.

Depended Upon Component (DOC): algo de lo que depende el SUT, una clase o
componente.

Shared fixtures: Es el conjunto de datos que se usan para la preparación de un tests en un medio externo. Por ejemplo un volcado de base de datos que se inserta antes de un tests para preparar el estado inicial previo a la ejecución.

Nota del traductor:  Agrego esta defición ya que es importante para entender contenido posterior y que el autor no ha incluído. Shared Fixture: http://xunitpatterns.com/Shared%20Fixture.html

¿Qué entendemos por difícil de probar?

The Wall ( El muro )

Cuando comenzamos a usar TDD, rápidamente nos topamos con un muro con zonas difíciles de probar. Quizás el ciclo simple de refactorización rojo-verde comienza a empantanarse cuando comenzamos a trabajar con el código de la capa de infraestructura que habla con la persistencia (base de datos) o un servicio web externo. Quizás no sepamos cómo testear nuestra UI (interfaz de usuario) con un framework xUnit. O tal vez teníamos una base de código heredada, y colocando incluso el la parte más pequeña bajo prueba se convirtió rápidamente en un maratón en lugar de en carreras cortas.

Los novatos en TDD a menudo encuentran que todo se vuelve un poco complicado y
frente a la presión del horario, dejan TDD. Al abandonar pierden la fe en
la capacidad de TDD y continúan con la presión de su horario. Todos hacemos
lo mismo, bajo presión recurrimos a lo que sabemos; cuando encontramos algunas dificultades con TDD, los desarrolladores dejan de escribir pruebas.

El denominador común entre las áreas difíciles de probar es que
romper el ritmo de desarrollo desde nuestros test rápidos y el ciclo de testeo, y son
costosos y lento de escribir. Las pruebas suelen ser frágiles y fallan erráticamente y son difíciles de mantener.

La base de datos

  • Tests lentos: Los tests de la base de datos se ejecutan lentamente, hasta 50 veces más lentamente que las pruebas normales. Esto rompe el ciclo de TDD. Los desarrolladores tienden a omitir la ejecución de todas las pruebas porque lleva demasiado tiempo.
  • Shared fixture bugs: una base de datos es un ejemplo de un dispositivo compartido. Un shared fixture comparte el estado en múltiples tests. El peligro aquí es que el test A y el test B pasan de forma aislada, pero ejecutar el test A después del test B cambia el valor de ese fixture para que el test falle inesperadamente. Este tipo de errores son costosos de rastrear y corregir. Terminas con un patrón de búsqueda binario para intentar resolver problemas de shared fixture: probar combinaciones de test para ver qué combinaciones fallan. Debido a que eso consume mucho tiempo, los desarrolladores tienden a ignorar o eliminar estas pruebas cuando fallan.
  • Tests oscuros: para evitar problemas de shared fixtures, las personas a veces intentan comenzar con una base de datos limpia. En el setup de su test, completan la base de datos con los valores que necesitan y, en el teardown, los limpian. Estas pruebas se vuelven oscuras, porque el código de setup y teardown agrega mucho ruido, lo que distrae de lo que realmente se está probando. Esto hace que los tests sean difíciles de leer, ya que son menos granulares y, por lo tanto, es más difícil encontrar la causa de la falla. El código de setup y teardown de la persistencia es otro punto de falla. Recuerda que el único test que tenemos para test en sí mismas es escribir un test que falle. Una vez que obtiene demasiada complejidad en su test, puede resultar difícil saber si su test está funcionando correctamente. También los hace más difíciles de escribir. Dedica mucho tiempo a escribir la configuración y eliminar el código que desvía su enfoque del código que está tratando de poner a prueba, rompiendo el ritmo TDD.
  • Lógica condicional: Los tests de bases de datos también tienden a terminar con lógica condicional; no estamos realmente seguros de lo que vamos a obtener, así que tenemos que insertar un check condicional para ver lo que obtuvimos. Nuestros tests no deben contener lógica condicional. Deberíamos poder predecir el comportamiento de nuestros tests. Entre otros problemas, probamos nuestros tests haciendo que fallen primero. Introducir demasiados caminos crea el riesgo de que los errores estén en nuestro test no en el SUT.

El UI / interfaz de usuario

  • No es el punto fuerte de xUnit: las herramientas de xUnit son excelentes para manejar una API, pero son menos buenas para manejar una IU. Esto tiende a deberse a que una interfaz de usuario se ejecuta en un marco que el ejecutor de pruebas necesitaría emular o con el que interactuar. Probar una aplicación WinForms necesita el bombeo de mensajes, probar una aplicación WebForms necesita un pipeline de ASP.NET. Las soluciones como NUnitAsp han demostrado ser menos efectivas para probar las IU que las herramientas de scripting como Watir o Selenium, que a menudo carecen de soporte para funciones como JavaScript en las páginas.
  • Pruebas lentas: las pruebas de IU tienden a ser pruebas lentas porque son de extremo a extremo, pasando por todo el stack hasta la persistencia.
  • Pruebas frágiles: las pruebas de IU tienden a ser frágiles, porque a menudo fallan en los intentos de refactorizar nuestra IU. Por lo tanto, cambiar el orden y la posición de los campos en la interfaz de usuario, o el tipo de control utilizado, a menudo romperá nuestras pruebas. Esto hace que las pruebas de IU sean costosas de mantener.

Los sospechosos de siempre

Podemos identificar una lista de los sospechosos habituales, que causan problemas.

  • Comunicaciones de red
  • Sistema de archivos
  • Requisitos de configuración del entorno
  • Una llamada fuera de proceso (incluye hablar con Db)
  • UI

Dónde encontrar más Patrones de XUnit

XUnit Patterns: El sitio y el libro de Gerard Meszaros son una lectura esencial si desea comprender los patrones involucrados en el desarrollo basado en pruebas.

Working with Legacy Code: el libro de Michael Feathers es la guía definitiva para el desarrollo de prueba primero en escenarios en los que se trabaja con código heredado que no tiene pruebas.

La próxima vez veremos cómo resolvemos estos problemas.

Deja una respuesta

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

 

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.