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.

Avoid Testing Implementation Details, Test Behaviours / Evita testear detalles de implementación, testea comportamiento

Traducción del texto original de Ian Cooper el 6 de Octubre de 2011 en http://codebetter.com/iancooper/2011/10/06/avoid-testing-implementation-details-test-behaviours/

De vez en cuando vuelvo a leer el libro Test-Driven Development de Kent Beck. Sinceramente, creo que es uno de los mejores libros de desarrollo de software jamás escritos. Lo que me encanta del libro es su sencillez. Tiene una austeridad que engaña, como si se tratara de una aproximación superficial del tema. Pero si continúas leyendo a medida que avanzas con TDD te das cuenta que es bastante completo. Donde trabajos posteriores a este libro han agregado páginas e ideas, a menudo han disminuido la técnica, cargándola de complejidad, generando malentendidos acerca del poder de TDD. Insto a cualquiera de vosotros que lo haya practicado TDD por un tiempo, a volver a leer este libro regularmente.

Para mí, una de las ideas clave de TDD, que a menudo se pasa por alto, se expresa en el ciclo: Red, Green, Refactor. Primero escribimos una prueba que falle, luego la implementamos el código de la manera más rápida y sucia que podemos, y luego la hacemos elegante. En este post del blog, quiero hablar sobre algunas de las ideas del segundo y tercer paso que se pierden. (Aunque debe recordar el primer paso: las pruebas no tienen pruebas, por lo que debe probarlas).

Mucha gente se bloquea cuando van a implementar, porque tratan de implementar bien, pensando en buenos patrones OO, SOLID, etc. ¡Alto! El objetivo es poner el test en verde lo antes posible. No intentes crear una solución decente en este punto, simplemente escribe la solución más rápida para el problema que puedas encontrar. Corta y pega si puedes, copia algoritmos de CodeProject o StackOverflow si puedes.

Del libro Test-Driven Development.

Detente. Espera. Puedo escuchar a los «estirados» burlándose entre ustedes y votimando. ¿ copiar y pegar? ¡ El fin de la abstracción ! ¡ La muerte del clean code !

Si estás molesto, respire profundamente. Aspira por la nariz … aguanta 1, 2, 3 … expira por la boca. Ahora. Recuerda, nuestro ciclo tiene diferentes fases (pasan rápidamente, a menudo en segundos, pero son fases):

1. Escriba una prueba.

2. Haz que compile.

3. Ejecútalo para ver si falla.

4. Hazlo funcionar.

5. Elimina la duplicación .

Es ese paso final (que es nuestro paso de refactorización) es en el que hacemos clean code. No antes, sino después.

Hacer clean code al mismo tiempo que hacemos el paso «que funcione» puede ser demasiado para hacer a la vez. Tan pronto como puedas, comprueba el paso «hágalo funcionar» y luego «limpie el código» cuando lo desees.

Ahora bien, aquí hay una implicación importante que a menudo se pasa por alto. No tenemos que escribir tests para las clases que refactorizamos a través de ese último paso. Serán implícitos a nuestra implementación y ya están cubiertos por la primera prueba. No es necesario que agreguemos nuevas pruebas para el siguiente nivel, a menos que consideremos que no sabemos cómo dirigirnos al siguiente paso.

Del libro Test-Driven Development nuevamente:

Debido a que estamos usando Pairs como claves, tenemos que implementar equals() y hashCode(). No voy a escribir tests para estos métodos, porque estamos escribiendo código en el contexto de una refactorización. Si estamos en el paso de refactorización y pasan todos los tests, entonces suponemos que ese nuevo código se ha ejercitado correctamente. Si estuviera programando con alguien que no viera exactamente a dónde vamos con esto, o si la lógica se volviera un poco compleja, comenzaría a escribir pruebas por separado.

¡ El código desarrollado en el contexto de la refactorización no requiere nuevos tests ! Ya está cubierto, y las técnicas de refactorización seguras significan que no deberíamos estar introduciendo cambios específicos, sólo limpiando la implementación aproximada que solíamos hacer en verde. En este punto, más pruebas te ayudarán a dirigir el test (pero tienen un coste)

El aprendizaje es que un test por clase no es la esencia de TDD. Agregar una nueva clase no es el desencadenante para escribir un test. El motivo que lleva a escribir un test es implementar un requisito. Por lo tanto, deberíamos probar de afuera hacia adentro (aunque recomendaría usar puertos y adaptadores y hacer el ‘afuera’ del puerto), escribir pruebas para cubrir los casos de uso (escenarios, ejemplos, GWT, etc.), pero sólo escribir pruebas para cubrir los detalles de implementación de eso cuando necesitemos comprender mejor la refactorización de la implementación simple con la que comenzamos.

Un resultado positivo de esto será que gran parte de nuestra implementación será interna o privada, y nunca se expondrá fuera. Eso reducirá el acoplamiento de nuestra solución y facilitará que efectuemos el cambio.

Del libro Test-Driven Development nuevamente:

En TDD usamos la refactorización de una manera interesante. Por lo general, una refactorización no puede cambiar la semántica del programa bajo ninguna circunstancia. En TDD, las circunstancias que nos preocupan son los tests que ya están pasando.

Cuando refactorizamos, no queremos romper los tests. Si nuestros tests saben demasiado sobre nuestra implementación, será difícil refactorizar, porque los cambios en nuestra implementación necesariamente darán como resultado que reescribamos los tests, momento en el que no estamos refactorizando. Diríamos que hemos sobreespecificado a través de nuestros tests. En lugar de ayudar al cambio, nuestros tests ahora han comenzado a ser un obstaculo. Como alguien que ha cometido este error, puedo dar fé del hecho de que es posible escribir demasiados tests unitarias para testear detalles. Sin embargo, si seguimos TDD como se pensó originalmente, los tests caerán de forma natural en mayor medida en nuestra interfaz pública, no en los detalles de implementación, y nos resultará mucho más fácil cumplir con el objetivo de cambiar los detalles de impementación (cambio seguro) y no la interfaz pública (cambio inseguro). Por supuesto, esta observación, que muchos practicantes de TDD han pasado por alto esta idea, formó el base de BDD (original de Dan North) y es la razón por la que BDD se centró en escenarios y se enfoca afuera hacia adentro (inside-out) para TDD.

ZTE F680 telnet jail

Cuando accedemos a un ZTE F680 v4 al servicio telnet root/root` lo normal es que tengamos una jaula en la shell que no permite hacer mucho, como vemos en este ejemplo

$ telnet 192.168.1.1
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
F680
Login: root
Password: 

BusyBox v1.01 (2017.09.15-03:17+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

/ # ps
/bin/sh: Access Denied.

/ # help
Welcome,Built-in commands:
cat /proc/cpuusage  --show CPU usage
cat /proc/meminfo  --show memory usage
cat /proc/net/arp  --show ARP table
cat /proc/sys/net/netfilter/nf_conntrack_max --show total NAT table size
cat /proc/net/nf_nat_count  --Show current NAT table size
cat /proc/net/nf_conntrack_nat  --show NAT table entries
iptables -t filter -L -vn  --show iptables filter
iptables -t mangle -L -vn  --show iptables mangle
iptables -t nat -L -vn  --show iptables nat
ifconfig  --network interfaces configuring
sendcmd 1 wlan_mgr channel 1  --set wlan 2.4G channel
sendcmd 1 wlan_mgr channel 2  --set wlan 5G channel
/ # sh
/bin/sh: Access Denied.

Esta jaula de telnet limita el acceso y la ejecucion a unos poco comandos que sirven unicamente para el diagnostico del equipo.

Aun asi hay varias formas de evitar esta limitacion, espero poder poder compartiras pronto.

ZTE F680 V4 Virgin

Uno de mis proyectos personales, que me da muchas alegrías y dolores de cabeza a partes iguales, es tratar de tener dispositivos de comunicaciones que pueda gestionar libremente. ¿ Por qué ? Porque me preocupa la seguridad. Los dispositivos de red, normalmente son bastante inseguros con fallos típicos de otros modelos, marcas o mantienen fallos a lo largo de la corta vida de firmwares publicados. Bien es cierto que los últimos años parece que los fallos son menos exagerados, y que parece que se publican algún que otro parche más, pero creo que la cantidad de bugs corregidos y fallos de seguridad que se descubren cada día, lo hace insuficiente.

Por otro lado, gestionar mi propio dispositivo me permite asignar mi propia configuración de dns, nivel de seguridad, configuración de puertos, actualzaciones de seguridad, eliminar usuarios no deseados que pueden acceder a mi dispositivo, elegir qué IP’s pueden acceder a la gestión de mi dispositivo, quitar servicios, etc…

Es cierto que un operador necesita tener ciertas herramientas para poder dar soporte técnico a los clientes, necesita provisionar los positivos, diagnosticarlos etc… pero esto debería ser flexible con los clientes y sobretodo debería velar por la seguridad de sus abonados. Me refiero a mantener hardware actualizado y seguro. Todos tenemos un montón de cacharros obsoletos en cajas en el fondo del armario que nunca se usarán. Es una pena, el gasto energético y de recursos naturales que se necesitan para producir hardware tirados a la basura por que los grandes operadores o fabricantes no salen del ciclo de la venta y compra constante. Tendríamos que diseñar hardware para durar y optimizar durante años, exprimir lo que hay. Actualmente no hay restricciones y ese camino fácil no nos trae ninguna ventaja.

Estos motivos son los que unidos a puro placer del conocimiento tecnológico me llevan a cacharrear con dispositivos, entenderlos y aprovechar esa ventaja para mi beneficio personal: aumentar mi sensación de seguridad, que no es mucha 🙁

Ha llegado a mis manos un dispositivo ZTE F680 V4 de la compañía Virgin, y estoy trasteando con él. Estos son las principales diferencias que he encontrado:

  • Sa la salida de texto por puerto serie, está desactivada, hay que activarla en el CFE
  • CFE bastante recortado con pocos comandos disponibles, pero tenemos dn (Dump NAND)
  • Password credenciales típicas no funcionan root/Zte521
  • Password de admin de virgin tkE*2312
  • Aproximación de acceso vía TR-069 no funciona

búsqueda incremental en el historial de bash

Una utilidad muy poderosa y que pasó desapercibida durante muchos años para mí, ha sido la búsqueda incremental que aparece presionando <control+r> en la linea de commandos

(reverse-i-search)`':

Esta cosa rara que alerta y desconcierta cuando aparce por sorpresa es ahora una de mis grandes aliados en mi día a día.

Esta utilidad muestra los comandos previamente escritos a medida que introduces letras y matchea con comandos almaceandos. En ese momento le das al enter y vuelves a ejecutar ese comando, de una forma más rápida.

Si el comando coincide con lo que busas pero NO es el que quieres, puedes iterar volviendo a presionar <control+r> lo que hace volver a buscar en la lista, o <control+s> para ir en sentido inverso de la lista, por si fuiste muy rápido y te pasaste el resultado.

Este inteface tiene mucha más magia, que puedes encontrar en el manual, no es muy claro pero es un comienzo para probar. https://www.gnu.org/software/bash/manual/html_node/Commands-For-History.html

Recordad que tu historial de comandos está en ~/.bash_history si usas bash, en ~/.history o en el fichero indicado en la variable echo $HISTFILE

conserguir clave pública usando la clave privada

Chuleta rápida de cómo obtener la clave pública, la que repartimos tranquilamente por ahí si sólo tenemos la privada. Puede pasar que la hayas perdido y no la tengas a mano, aquí tienes una forma rápida de recuperarla usando tu propia clave privada ( la que nunca se comparte )

ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub

python-mode fails when updating submodule

Habitualmente uso vim +PluginUpdate para mantener mis plugins actualizados, y habitualmente me encuentro que python-mode falla con este error

[2020-01-14 18:21:34] Plugin klen/python-mode
[2020-01-14 18:21:34] $ cd '/home/jorge/.vim/bundle/python-mode' && git pull && git submodule update --init --recursive
[2020-01-14 18:21:34] > Already up to date.
[2020-01-14 18:21:34] > error: Server does not allow request for unadvertised object 837ecd3d7a8597ab5f28bc83072de68e16470f1e
[2020-01-14 18:21:34] > Fetched in submodule path 'submodules/pylama', but it did not contain 837ecd3d7a8597ab5f28bc83072de68e16470f1e. Direct fetching of

La forma de corregirlo es la siguiente:

jorge@portege:~$ cd .vim/bundle/python-mode
jorge@portege:~/.vim/bundle/python-mode (develop)$ git submodule sync
Synchronizing submodule url for 'submodules/astroid'
Synchronizing submodule url for 'submodules/autopep8'
Synchronizing submodule url for 'submodules/mccabe'
Synchronizing submodule url for 'submodules/pycodestyle'
Synchronizing submodule url for 'submodules/pydocstyle'
Synchronizing submodule url for 'submodules/pyflakes'
Synchronizing submodule url for 'submodules/pylama'
Synchronizing submodule url for 'submodules/pylint'
Synchronizing submodule url for 'submodules/rope'
Synchronizing submodule url for 'submodules/snowball_py'

De esta manera ya se puede volver a ejecutar vim +PluginUpdate sin problema.

fuente: https://github.com/python-mode/python-mode/issues/901

recuperar sesión de tmate

Tmate, Instant terminal sharing ( https://tmate.io/ ) es un multiplexador de terminales basdo en tmux (https://github.com/tmux/tmux/wiki) . Así a lo tosco, te permite compartir una o varias sesiones de terminal en la misma conexión. Es una de las herramientas que más intensamente uso para trabajar en equipo de manera remota. Tiene algunas complicaciones como atajos de teclado, configuraciones, etc… pero funciona realmente bien y aporta mucho más valor que problemas.

Si por lo que sea has cerrado el terminal, o no sabes cómo has salido de la sesión… aún puedes recuperarla… En /tmp/tmate-1000 tienes sockets a todas las sesiones abiertas. Siemplmente prueba uno a uno los sockets, entra con attachy decide si quieres recuperar el control de la sesión o matarla

tmate -S /tmp/tmate-1000/4nfqme attach

Recuerda también que si quieres recuperar la sesión ssh puedes hacerlo. Desde dentro de tu sesión ssh con control + b + colon. Donde colon son los dos puntos :

Eso te acceso a un modo de operación con tmate. El comando show-messages te dará información sobre el acceso a tu sesión

Wed Jul 17 14:20:00 2019 [tmate] Connecting to ssh.tmate.io…
Wed Jul 17 14:20:00 2019 [tmate] Note: clear your terminal before sharing readonly access
Wed Jul 17 14:20:00 2019 [tmate] web session read only: https://tmate.io/t/ro-DOKIIyahkHigjfF5JbHFr1Fdm
Wed Jul 17 14:20:00 2019 [tmate] ssh session read only: ssh ro-DOKIIyahkHigjfF5JbHFr1Fdm@ln2.tmate.io
Wed Jul 17 14:20:00 2019 [tmate] web session: https://tmate.io/t/FLb5IYsUyjZpp2dIdGuznh2NZ
Wed Jul 17 14:20:00 2019 [tmate] ssh session: ssh FLb5IYsUyjZpp2dIdGuznh2NZ@ln2.tmate.io

Como puedes ver tienes tanto acceso de sesión con el usuario guest o de sólo lectura. Además de acceso vía web.