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.

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

Compilar openssl para arm + uclibc

Necesitas descargar el compilador de arm y el la version de openssl que necesites


export CC="/home/embebed/compiler/cross-compiler-armv5l/bin/armv5l-cc"
export LD="/home/embebed/compiler/cross-compiler-armv5l/bin/armv5l-ld"
./Configure linux-armv4 -D__ARM_ARCH__=4 --prefix=/usr/local/openssl/ --openssldir=/usr/local/openssl shared
make

Reduce el tamaño de un pdf con páginas escaneadas

Un fichero pdf puede ser muy útil y ligero, siemre y cuando el contenido no sean imágenes escaneadas, es necesario recomprimir esas imágenes y volver a montar el pdf.

Como solución sorprendente he usado este par de comandos que unidos reducen el tamaño una proporción brutal. En un ejemplo real de 35 Mb a 1,8Mb y de 59Mb a 2,3Mb

Tan solo cambia el fichero de input.pdf con el nombre de tu fichero, se generara un fichero medium.pdf y un fichero small.pdf. Elije al gusto.

gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/screen -dNOPAUSE -dQUIET -dBATCH -sOutputFile=medium.pdf input.pdf
ps2pdf medium.pdf small.pdf