Movidas técnicas

Tecnología, ordenadores y todo eso.

He conseguido arreglar mi transmisor de radio

(Publicada originalmente en inglés en Google+).

Hoy he mirado atentamente a la señal que salía de mi antena y, sí, al final resultó que la mayor parte de la energía se gastaba fuera de la frecuencia en la que quería transmitir. Sin embargo, no fue debido a que las imágenes y transmisiones espúreas se llevaran la mayor parte de la energía... fue debido a que estaba transmitiendo en la frecuencia equivocada.

Seguid leyendo, que no se trataba de un simple caso de manazas girando la rueda de selección de frecuencias.

En el mundo de la radio definida por software (SDR, por las siglas en inglés) trabajamos con receptores y transmisores bastante básicos, cuya función es recibir una señal de radio y transformarla en una forma en la que el ordenador pueda procesarla, y viceversa. Para conseguirlo, se utilizan muchas matemáticas en el ordenador, usando algo a lo que llamamos "números complejos". Estos números complejos tienen dos componentes: una parte real y una parte imaginaria (el nombre "imaginaria" lo recibió cuando los matemáticos aún no entendían bien los números complejos y pensaban que estos números eran menos "reales" que los números que habían estado utilizando hasta aquel momento. Pero me estoy yendo por las ramas).

Por lo tanto, un receptor de radio SDR toma una señal de radio, realiza ciertas manipulaciones electrónicas y genera dos señales que el ordenador convierte en números complejos. Una de estas señales, llamada I, se convierte en las partes reales de esos números, y la otra señal, llamada Q, se convierte en las partes imaginarias.

Para transmitir, el ordenador toma las partes reales de los números complejos y genera una señal I, y también genera la correspondiente señal Q a partir de las partes imaginarias de los mismos números. Estas dos señales van al transmisor SDR, sufren ciertas transformaciones electrónicas, y se convierten en una señal de radio.

El problema que tenía yo es que los cables de las señales I y Q estaban cruzados en el transmisor.

El efecto resultante de este cruce de cables es que la señal salía transmitida por una frecuencia incorrecta. Normalmente tenía mi "frecuencia central" fijada en 14,080 MHz y mi frecuencia de transmisión fijada en 14,097 MHz. Sin embargo, como I y Q estaban cruzados, el transmisor tomó la parte real como imaginaria y la parte imaginaria como real, lo que hizo que la señal transmitida se "reflejase" alrededor de la frecuencia central y acabase teniendo una frecuencia de 14,063 MHz. ¡Ups!

Por suerte, estaba utilizando una antena de bucle magnético, que tiene un ancho de banda limitado (esto lo expliqué en la historia de ayer), así que es bastante probable que sólo haya salido transmitida una pequeña fracción de la energía de esta señal, y el resto se haya convertido en calor en la antena.

Ahora bien, no sé si lo he mencionado, pero ya se habían recibido mis señales unas tres o cuatro veces durante las anteriores dos semanas. Además, al utilizar una radio de onda corta para escuchar las transmisiones, podía oír mi señal con toda claridad en la frecuencia correcta. Es natural preguntarse cómo puede ser esto posible, si la señal estaba siendo emitida con la frecuencia equivocada.

La solución es simple: antes de que los números complejos producidos por el ordenador lleguen al transmisor, antes tienen que pasar por una tarjeta de sonido que los convierte en las señales eléctricas I y Q, y luego viajan hasta el transmisor por cables de audio. Cables de audio estéreo, en particular, que tienen tres hilos: un hilo de tierra, un hilo para el altavoz izquierdo, y un hilo para el altavoz derecho. En esta configuración, uno de los hilos de los altavoces porta la señal I y el otro hilo porta la señal Q. Cuando hay dos hilos paralelos por los que circulan corrientes alternas, se inducen pequeñas corrientes mutuamente, lo que causa una pequeña interferencia.

Con esta interferencia, cuando las señales llegan al transmisor de radio, el hilo de la señal I también porta un poquito de señal Q y viceversa, por lo que cuando se produce la transmisión acaba habiendo una pequeña señal en la frecuencia deseada originalmente. Esta señal podía yo oírla en mi radio porque estaba bastante cerca de la antena, y a veces se oía desde más lejos porque estaba utilizando WSPR, un modo digital para señales de baja potencia, así que la gente que recibía esas señales ya estaba esperando recibir transmisiones muy débiles.

Después de arreglar este fallo envié otra transmisión mediante WSPR. En esta ocasión me oyeron cuatro estaciones; la más lejana de ellas estaba en Alaska. Mi segunda señal se recibió en Nueva Zelanda. Ambas transmisiones se realizaron con 1 vatio de potencia. Creo que puedo decir con seguridad que tanto el transmisor como mi antena casera funcionan correctamente :)

Ahora sólo me queda arreglar mi amplificador de 50 vatios.

Mejoras en mi antena de radioaficionado casera

(Publicado originalmente en inglés en Google+.)

Ayer trabajé un poco en mi antena de bucle magnético. Una forma más correcta de denominarla sería "antena de bucle magnético pequeño", o SMLA en sus siglas en inglés. Consiste en un cable que forma un círculo y está conectado a un condensador variable. El bucle de cable forma una inductancia, que junto con la capacitancia del condensador forma un circuito resonante. Realizando las conexiones adecuadas se puede utilizar una SMLA para recibir y transmitir señales en la frecuencia de resonancia de la SMLA, que se puede variar girando una rueda para variar la capacitancia del condensador.

Como podéis imaginar, cuanto más resonante sea la antena, mejor es: las señales presentes en esa frecuencia se amplifican más. Además, cuando la antena es más resonante tiene un ancho de banda menor: la energía presente en la antena se concentra en una banda de frecuencias más estrecha. La cantidad de resonancia viene dada por un número llamado "factor de calidad", o Q. Al factor Q lo afectan los valores de la inductancia, capacitancia y resistencia de la SMLA. En particular, cuanto menor sea la resistencia, mayor será el valor de Q. Al fabricar una SMLA es importante reducir lo máximo posible la resistencia eléctrica para obtener el mejor valor de Q.

Hay otro motivo por el que es importante reducir la resistencia eléctrica si queremos fabricar una SMLA capaz de transmitir: la resistencia a la radiación de la antena es muy pequeña, en el orden de miliohmios, así que cualquier resistencia adicional reduce muchísimo la eficiencia de la antena.

La gente como yo, que estamos acostumbrados a tratar con corrientes continuas, tendemos a pensar que para lograr esto tenemos que utilizar cables de calibre grueso, soldar todas las conexiones para reducir las pérdidas de contacto, etc. Hace un par de semanas medí la resistencia de mi SMLA y obtuve un valor de 50 miliohmios, que no suena tan mal; sin embargo, el factor Q de mi antena parecía bastante bajo y nadie era capaz de oír mis transmisiones.

De lo que no me había dado cuenta es de que las corrientes alternas (y las ondas de radio que circulan por un cable son corrientes alternas) no viajan ocupando toda la sección del cable, como lo hacen las corrientes continuas: existe un fenómeno llamado "efecto pelicular" por el que esas corrientes sólo circulan por la superficie del conductor (por su "piel"). Cuando mayor es la frecuencia, menos profundidad tiene esa piel: por ejemplo, en el cobre, a 14 MHz, la mayor parte de la corriente circula a menos de 17 micrómetros de profundidad.

La primera consecuencia de este efecto es que la resistencia de un cable no se reduce con el cuadrado del diámetro de su sección como ocurre con corrientes continuas, sino que se reduce linealmente con el diámetro. Por lo tanto, el uso de cable de grueso calibre no ayuda mucho. Lo que hay que utilizar en su lugar es cinta de cobre, ya sea plana o trenzada. La cinta trenzada tiene mucha área de superficie para su volumen, así que debería presentar una resistencia baja a la corriente alterna.

La segunda consecuencia es que se deben evitar las soldaduras: ya que la corriente circula por la superficie, los puntos de la superficie que estén cubiertos de estaño tendrán una conductividad menor que la superficie de cobre desnudo.

Teniendo esto en cuenta, ayer rehice las conexiones entre el bucle de cable y el condensador de mi SMLA, sustituyendo los cables de grueso calibre por cintas de cobre trenzadas. Las conecté utilizando tornillos y arandelas de manera que estuviesen bien apretadas contra los terminales del bucle y del condensador, asegurándome de que se esté tocando toda el área de superficie posible.

Con este cambio parece que ha aumentado el factor Q de mi SMLA: ahora puedo utilizar un ancho de banda de unos 40 kHz antes de tener que resintonizar la antena, mientras que antes podía utilizar unos 60 kHz. Esperaba ver también una mejora en el rendimiento en transmisión, pero lamentablemente nadie oyó mis transmisiones en todo el día de hoy. Supongo que mi antena aún no es lo bastante buena.

Puede que haya otra explicación a esta incapacidad de hacerme oír, no obstante. Usando una radio de onda corta pude oir señales espúreas alrededor de la señal que quería transmitir, y usando un receptor RTLSDR pude ver el espectro de radio que rodea a la frecuencia en la que mi transmisor estaba sintonizado, y había muchas espúreas e imágenes durante la transmisión. No sé si es un fallo de mi transmisor en particular o un fallo de diseño. En cualquier caso, esto me sugiere que tal vez se esté desperdiciando mucha energía en esas espúreas. Eso es definitivamente algo que tendré que investigar de nuevo a fondo.

Por qué a una señal de radio en código Morse la llamamos Onda Continua a pesar de que se enciende y se apaga

(Publicada originalmente en inglés en Google+).

Como muchos sabréis, últimamente me estoy dedicando a la radioafición. En este mundillo, el código Morse todavía se utiliza mucho, aunque ya no es necesario aprenderlo para sacarse la licencia de radioaficionado. Cuando dos operadores utilizan el código Morse para comunicarse, muchas veces utilizan un modo llamado "Onda Continua".

Durante bastante tiempo pensé que el de "Onda Continua" era un nombre bastante raro para un sistema de transmisión de código Morse. Podía constatar que, en efecto, hay una onda, que es la onda de radio sobre la que se modula el código Morse. Lo que no veía muy claro era el motivo del uso del adjetivo "continua". Al fin y al cabo, la onda se está encendiendo y apagando todo el rato; es precisamente la forma en la que se puede transmitir Morse. Si se enciende y se apaga, la onda no es continua. ¿Qué era lo que me faltaba por saber?

Lo que me faltaba por saber era que antes de que existiese la Onda Continua, ya había código Morse en la radio, que se transmitía utilizando un tipo de onda distinta: la Onda Amortiguada.

Una Onda Continua es una onda senoidal con una frecuencia determinada. Hoy en día nos es muy fácil producir ondas senoidales precisas y estables utilizando circuitos electrónicos bastante baratos. Sin embargo, en los primeros días de la radio no era así: no había circuitos osciladores electrónicos lo suficientemente buenos como para producir ondas continuas de calidad, así que las emisoras de radio utilizaban un mecanismo diferente para producir un tipo de ondas de radio diferente.

Este mecanismo era el transmisor a chispa. La idea general es que cuando hay un alto voltaje entre dos conductores separados por un espacio vacío se produce un arco eléctrico (una chispa). El transmisor contiene un circuito que, al prenderse el arco, produce una oscilación resonante, como el sonido de una campana golpeada por su badajo. Esta oscilación se pasa a una antena para transmitirla en forma de onda de radio, llamada "onda amortiguada" porque pierde amplitud con el tiempo, igual que el tañido de una campana.

Como la onda amortiguada sólo dura una minúscula fracción de segundo, el espacio en el que se produce la chispa está fabricado de tal manera que estas chispas se extinguen justo después de encenderse, y una nueva chispa se prende casi de inmediato, lo que produce una nueva onda amortiguada. De esta manera se producen muchas ondas amortiguadas cada segundo, igual que un timbre que suena de forma aparentemente continua porque el martillo golpea la campana muchas veces por segundo.

El inconveniente de los transmisores a chispa es que son muy ineficientes y producen una cantidad prodigiosa de interferencias, así que se invirtió mucho esfuerzo en descubrir una buena manera de generar una "onda continua" que no pierda potencia con el tiempo, de manera que se pueda producir una sola onda que luego se puede encender y apagar según se necesite.

Con el tiempo se desarrollaron varios sistemas, como generadores eléctricos de alta frecuencia, osciladores electrónicos, etc. Según éstos se hicieron más comunes, los viejos transmisores a chispa y las ondas amortiguadas que producían fueron relegados y, finalmente, prohibidos mundialmente (así de problemáticas eran las interferencias que producían).

Y ese es el motivo por el que a una señal de radio en código Morse la llamamos Onda Continua a pesar de que se enciende y se apaga.

Announcing Debloat: a new open source project for data compression

I'm happy today because I can finally announce my latest project, Debloat.

Debloat is an Enterprise-ready, extensible Java data compression/decompression framework. It supports many different compression algorithms and data encoding formats that can be plugged in as needed, using simple configuration files written in XML, or programmatically using a very simple, intuitive DSL. Debloat is Open Source and available under the terms of the Apache License 2.0.

The typical problem with data compression algorithms is that the output they produce often consists in opaque binary blobs, which nobody can inspect to see what's inside, and it's very hard to write tools that can operate on those blobs. Debloat solves that by using a new codec that produces easily parsed, human-readable XML files!

For example, assume you would like to compress the following string:

Trololololo lololo lololo.

A regular compression library would produce something that perhaps looks like this:

▼ï◘┘üöO♥♂)╩╧üB♣¶JÅ♂ê╝P!←

You can see that this is a complete opaque bag of random symbols corresponding to various binary values. However, Debloat produces well-formed output that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<compressedData algorithm="lz77">
  <byte value="84"/>
  <byte value="114"/>
  <byte value="111"/>
  <byte value="108"/>
  <reference distance="2" length="7"/>
  <byte value="32"/>
  <reference distance="7" length="13"/>
  <byte value="46"/>
  <byte value="10"/>
</compressedData>

Undoubtedly, this format has many, many advantages for Java Enterprise applications respect to the opaque binary blob.

I hope you try Debloat out and post your feedback on Google+! I have many ideas for the next few versions of Debloat, hopefully they'll be released soon!

Guaranteed compression

A few eons ago I was a member of a network called Fidonet, which had an echoarea (the equivalent of a mailing list) dedicated to file compressors. As I was fascinated by them at the time, I was naturally a subscriber.

Every once in a while someone would come in and ask if there could be a compressor that were guaranteed to compress the input data by at least one byte. The response was invariably that no, there couldn't be one, and the argument was based on the pigeonhole principle: if there were such a compressor, you could use it to compress some data, then use it on the compressed data to compress it even more, and so on until you are left with a byte, which would mean that there are only 256 possible distinct files in all of the Universe. This is clearly absurd, so there can't be such a compressor.

While I agree with the conclusion, I find the argument unsound, so let's explore it with the help of a close cousin of this compressor everyone asked about.

There is a compressor that is guaranteed to either produce an output the same size of the input, or produce an output one bit smaller than the input. This compressor simply interprets the input data as a big binary number and subtracts 2 from it. The corresponding decompressor is also very easy: just interpret the input data as a big binary number and add 2 to it.

It is easy to prove that the compressor will always produce an output file that is either the same number of bits as the input file, or 1 bit smaller. Therefore, you can use the compressor, get an output file, use the compressor on the output file, get a new output file, use the compressor on the new output file, and so on until you get a file that only contains 1 bit that can be 0 or 1. According to the argument above, this would mean that only two documents exist in the whole Universe; however this is clearly not true, so what's the catch?

The catch is that we can't reconstruct the original document with the compressor's output alone (the 0 or 1 bit). We need more information to do that: we need to know how many times we applied the compressor so we can apply the decompressor an equal number of times to reconstruct the original file. So let's add the number of compression operations to the last compressed file. How big is the resulting file?

The compressor works by subtracting 2 from the number we interpret the input file as. If I apply the compressor once, I will have subtracted 2; if I apply it twice, I will have subtracted 4 from the original file; if I apply it 1000 times I will have subtracted 2000. To get a single bit I need to apply the compressor enough times that the original file minus twice the number of applications is 0 or 1. That means that, if I call the original file "i", the ultimately compressed bit "c" and the number of compressions "n", I have that i=2*n+c.

Therefore, to compress the input file i you will need n operations, and n is one half of i. You will now need to append the number n to the output of the last compression step to be able to reconstruct the original file. The input file i has b bytes, and n is one half of i, so n has b-1 bytes. Therefore, you will have to store 1 bit for the compressed data, plus b-1 bits for n, which amounts to b, which is the length of the original file!

So yes, while it is not true that being able to reduce a file to 1 bit or 1 byte by successive compressions means that there are only 2 or 256 files in the Universe, it is still true that doing so will be of no benefit.

Comments are welcome — on the Google+ post.

Tests de integración y compilación continua

En esta última entrega de mi serie sobre tests de unidad, quiero hablaros brevemente de dos asuntos relacionados con este tema: tests de integración y compilación continua.

En estos artículos os he explicado cómo escribir tests que pongan a prueba los elementos de vuestro software por separado, libres de la influencia de los otros elementos. Sin embargo, estas pruebas no son suficientes: tener varios elementos que funcionan correctamente por separado no significa que vayan a funcionar bien al ponerlos juntos. Por ejemplo, puede ser que uno de nuestros módulos espere fechas en formato día-mes-año y la base de datos las produzca en formato mes-día-año; tal vez nuestro módulo funcione perfectamente al igual que la base de datos, pero cuando intentemos pasar una fecha entre uno y el otro tendremos un problema bastante gordo.

Para evitar esto tenemos que introducir un nuevo tipo de test: los tests de integración. Grosso modo, los tests de integración consisten en “ensamblar” varios módulos, o incluso el programa entero, y comprobar que varias acciones tienen los efectos deseados. Un caso especial de test de integración es el “end-to-end test” (“prueba de un extremo al otro”), que consiste en arrancar todo el programa (configurado para acceder a bases de datos específicas para pruebas), realizar operaciones en el interfaz de usuario utilizando una herramienta de automatización, y comprobar que estas operaciones se reflejan en la base de datos.

Por su naturaleza, los tests de integración se ejecutan más lentamente que los tests de unidad, así que puede llevar mucho tiempo ejecutarlos. Por ese motivo, los programadores normalmente no ejecutan los tests de integración de forma rutinaria, sino que lo hace el servicio de compilación continua.

Un compilador continuo (“continuous build”) es un servicio que “vigila” el sistema de control de versiones y, cuando detecta que alguien ha hecho “commit” de una nueva versión del software, la descarga, la compila y ejecuta todos los tests. Los compiladores continuos suelen estar conectados a varios sistemas de notificación de estado, como email, páginas web, pantallas, o incluso semáforos. Estos sistemas indican el estado de la última versión compilada por el servicio; “rojo” si hubo algún problema al compilar o ejecutar los tests, o “verde” si no hubo ningún problema.

En muchos sitios se utiliza el compilador continuo para asegurar la calidad del software. Por ejemplo, en muchos sitios está prohibido hacer “commit” si el compilador continuo está rojo, a menos que sea para arreglar el fallo. A la hora de escoger una versión para poner en producción, la elección es mucho más fácil: la última versión verde disponible. De hecho, en algunos sitios ponen en producción una nueva versión cada día; esta versión, por supuesto, es la última versión verde disponible.

Y con esto termino mi serie sobre tests de unidad. Espero que os haya inspirado para empezar a escribir y mantener tests de unidad en vuestro software si no lo hacíais antes, y para aprender más sobre el tema si ya lo hacíais. Es posible que en el futuro escriba sobre otros asuntos técnicos; sólo tenéis que escribirme para sugerir temas.

(Primer artículo).

Tests de unidad en lenguajes dinámicos

En esta serie os he hablado de varios temas relacionados con los tests de unidad, pero siempre he usado Java para los ejemplos. Si preferís utilizar lenguajes dinámicos como Python, Ruby o JavaScript, los mismos principios sirven, con una excepción que os facilitará muchísimo la vida.

En muchos de los lenguajes dinámicos más populares las funciones y clases son objetos de “primera clase”, así que es posible manipularlos igual que se puede manipular cualquier objeto: se pueden asignar a una variable, se pueden pasar como argumentos de una función, y, lo más crucial, se les puede asignar nuevos valores. Esto significa que, en estos lenguajes dinámicos, no es necesaria la inyección de dependencias para utilizar dobles para pruebas: sólo tenéis que asignar vuestro doble a la clase o función que queréis sustituir.

Vamos a ver un ejemplo en Python. He escrito este pequeño programita que muestra la temperatura actual en un aeropuerto, usando los datos meteorológicos que se pueden descargar por Internet desde el servidor del NOAA (la agencia meteorológica y oceanográfica de los EEUU):

#!/usr/bin/python
# temperature.py

import sys
import urllib2

METAR_URL = 'ftp://tgftp.nws.noaa.gov/data/observations/metar/stations/'

class Error(Exception):
    pass

def GetTemperature(station):
    try:
        f = urllib2.urlopen(METAR_URL + station.upper() + '.TXT')
        lines = f.readlines()
        f.close()
        for line in lines:
            if not line.startswith('METAR '):
                continue
            fields = line.split(' ')
            for field in fields:
                if '/' in field:
                    temp, dew = field.split('/', 2)
                    if temp[0] == 'M':
                        return -int(temp[1:])
                    else:
                        return int(temp)
        raise Error('Invalid format')
    except urllib2.URLError, e:
        raise Error(e)

if __name__ == '__main__':
    if len(sys.argv) != 2:
        raise Error('Invalid arguments')
    print GetTemperature(sys.argv[1])

Este programa utiliza la biblioteca estándar urllib2 para conectarse al servidor FTP del NOAA, y descargar el fichero con las últimas observaciones para el aeropuerto elegido. Luego analiza el fichero, busca la primera línea que comienza por “METAR”, busca la temperatura y devuelve un entero, o lanza la excepción “Error” si hubo algún problema en algún punto de la función.

Si queréis, podéis probarlo y ejecutarlo en vuestra máquina; el programa toma un argumento que es el código ICAO del aeropuerto (el código de Barajas es LEMD; el del aeropuerto de Barcelona es LEBL; el de Santiago es LEST) y muestra un número en pantalla que es la temperatura en grados centígrados (o un volcado de pila si hubo un error).

Para escribir los tests de unidad en Python se utiliza PyUnit; cada test es un método cuyo nombre comienza por “test” en una clase que deriva de unittest.TestCase. Éste es un esqueleto para los tests de unidad:

#!/usr/bin/python

import unittest

class TemperatureTest(unittest.TestCase):

    def testGetTemperature(self):
        # código del test
        pass

if __name__ == '__main__':
    unittest.main()

Ahora sólo tenemos que crear nuevos tests que prueben los principales casos en que nos podríamos encontrar a la hora de llamar a la función GetTemperature. Esto podría tener este aspecto si (de momento) no nos preocupásemos de las dependencias:

#!/usr/bin/python

from StringIO import StringIO
import unittest

import temperature


class TemperatureTest(unittest.TestCase):

    def testGetPositiveTemperature(self):
        self.assertTrue(0 < temperature.GetTemperature('DNAA'))

    def testGetNegativeTemperature(self):
        self.assertTrue(0 > temperature.GetTemperature('BGAA'))

    def testErrorOpeningUrl(self):
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'XXXX')

if __name__ == '__main__':
    unittest.main()

El primer test pide la temperatura de un aeropuerto de Nigeria y comprueba que su temperatura es positiva, el segundo pide la temperatura de un aeropuerto de Groenlandia y comprueba que la temperatura es negativa, y el tercero pide la temperatura de un aeropuerto inexistente y comprueba que la función lanza una excepción.

Hay al menos tres problemas bastante gordos con este test; el primero es el ya conocido de que los tests no deberían necesitar acceso a Internet para funcionar ni deberían depender de nada que no sea la propia función que se está probando. El segundo es un poco más insidioso: ¿quién nos garantiza que en Nigeria siempre habrá temperaturas sobre cero? ¿Quién nos garantiza que en Groenlandia siempre hará frío? ¿Y quién nos garantiza que el aeropuerto “XXXX” no existe? Nadie, nadie y nadie. El tercero es el más insidioso de todos: aún suponiendo que la función siempre nos devuelva valores positivos para Nigeria o negativos para Groenlandia, ¿cómo podemos comprobar que esos valores son correctos? Si nos dice que en Groenlandia hace -600 grados, eso es claramente incorrecto; pero si nos dice que hace -5, ¿es correcto o no? ¿Cómo puede el test saberlo? El test tendría que descargarse los datos del METAR, analizarlos y compararlos con el valor correcto... pero eso es meterse en un berenjenal de cuidado.

Para evitar todos estos problemas debemos utilizar un doble para pruebas que sustituya a la función urllib2.urlopen y proporcione un contenido controlado por nosotros; de esta manera siempre sabremos que la función devuelve lo que tiene que devolver. Si este programa estuviese hecho en Java tendría que inyectar una instancia de urllib2 para poder utilizar un doble para pruebas en el test; como es Python, en cambio, sólo tengo que asignar un nuevo valor:

def testGetPositiveTemperature(self):
    oldurlopen = temperature.urllib2.urlopen
    temperature.urllib2.urlopen = lambda url: StringIO('METAR ABCD 123456Z 12/34 7890\n')
    actual = temperature.GetTemperature('abcd')
    temperature.urllib2.urlopen = oldurlopen
    self.assertEqual(12, actual)

Como podéis ver, guardo la función urlopen antigua y la sustituyo por una que devuelve un objeto StringIO (que tiene el mismo interfaz que el objeto devuelto por urlopen, así que puede hacer las veces de objeto “fake”) con un contenido de ejemplo. Luego llamo a GetTemperature, restauro el valor antiguo de urlopen y compruebo que la función me ha devuelto el valor esperado.

Si escribimos varios tests podemos utilizar las funciones setUp y tearDown para guardar y restaurar el valor antiguo de urlopen antes y después de cada test:

#!/usr/bin/python

from StringIO import StringIO
import unittest

import temperature


class TemperatureTest(unittest.TestCase):

    def setUp(self):
        self._oldurlopen = temperature.urllib2.urlopen

    def tearDown(self):
        temperature.urllib2.urlopen = self._oldurlopen
    
    def testGetPositiveTemperature(self):
        temperature.urllib2.urlopen = lambda url: StringIO('METAR ABCD 123456Z 12/34 7890\n')
        self.assertEqual(12, temperature.GetTemperature('abcd'))

    def testGetNegativeTemperature(self):
        temperature.urllib2.urlopen = lambda url: StringIO('METAR ABCD 123456Z M12/M34 7890\n')
        self.assertEqual(-12, temperature.GetTemperature('abcd'))

    def testInvalidFormat(self):
        temperature.urllib2.urlopen = lambda url: StringIO('foo bar\n')
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')

    def testErrorOpeningUrl(self):
        def FakeUrlopen(url):
            raise temperature.urllib2.URLError('foo')
        temperature.urllib2.urlopen = FakeUrlopen
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')


if __name__ == '__main__':
    unittest.main()

Igual que en Java, en Python hay frameworks para construir objetos “mock” fácilmente; el que conozco es Mox, que funciona de forma bastante parecida a EasyMock. Con mox tenemos que crear una instancia de la clase Mox, y luego llamar a sus métodos CreateMock (para crear un mock de una clase) o CreateMockAnything (para crear un mock de cualquier objeto), ReplayAll para pasar a modo “replay” y VerifyAll.

Por ejemplo, si en el primer test que describí en este artículo sustituyésemos la función lambda por un mock, tendríamos algo similar a esto:

def testGetPositiveTemperature(self):
    m = mox.Mox()
    oldurlopen = temperature.urllib2.urlopen
    temperature.urllib2.urlopen = m.CreateMockAnything()
    mock_file = StringIO('METAR ABCD 123456Z 12/34 7890\n')
    temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(mock_file)
    m.ReplayAll()
    self.assertEqual(12, temperature.GetTemperature('abcd'))
    temperature.urllib2.urlopen = oldurlopen
    m.VerifyAll()

Como el patrón “guardar-asignar-restaurar” es tan habitual, Mox nos proporciona funciones para realizar esa operación fácilmente; la más habitual es StubOutWithMock, que sustituye cualquier objeto por un mock, como en el siguiente ejemplo:

def testGetPositiveTemperature(self):
    m = mox.Mox()
    m.StubOutWithMock(temperature.urllib2, 'urlopen')
    mock_file = StringIO('METAR ABCD 123456Z 12/34 7890\n')
    temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(mock_file)
    m.ReplayAll()
    self.assertEqual(12, temperature.GetTemperature('abcd'))
    m.VerifyAll()
    m.UnsetStubs()

Si reescribimos todos los tests para utilizar mocks en lugar de funciones escritas a mano, el fichero queda así:

#!/usr/bin/python

import mox
from StringIO import StringIO
import unittest

import temperature


class TemperatureTest(unittest.TestCase):
    
    def setUp(self):
        self._mox = mox.Mox()
        self._mox.StubOutWithMock(temperature.urllib2, 'urlopen')

    def tearDown(self):
        self._mox.UnsetStubs()

    def testGetPositiveTemperature(self):
        metar = StringIO('METAR ABCD 123456Z 12/34 7890\n')
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(metar)
        self._mox.ReplayAll()
        self.assertEqual(12, temperature.GetTemperature('abcd'))
        self._mox.VerifyAll()

    def testGetNegativeTemperature(self):
        metar = StringIO('METAR ABCD 123456Z M12/M34 7890\n')
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(metar)
        self._mox.ReplayAll()
        self.assertEqual(-12, temperature.GetTemperature('abcd'))
        self._mox.VerifyAll()

    def testInvalidFormat(self):
        metar = StringIO('foo bar\n')
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndReturn(metar)
        self._mox.ReplayAll()
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')
        self._mox.VerifyAll()

    def testErrorOpeningUrl(self): 
        temperature.urllib2.urlopen(temperature.METAR_URL + 'ABCD.TXT').AndRaise(temperature.urllib2.URLError('foo'))
        self._mox.ReplayAll()
        self.assertRaises(temperature.Error, temperature.GetTemperature, 'abcd')
        self._mox.VerifyAll()


if __name__ == '__main__':
    unittest.main()

Y con esto hemos llegado casi al final de la serie sobre tests de unidad. En la próxima, y última, entrega aclararé unas cuantas cosas que se me han quedado en el tintero y contestaré las preguntas que me enviéis. El que tenga alguna duda, que hable ahora o calle para siempre :)

(Primer artículo, siguiente artículo).

Introducción a los dobles para pruebas

Como ya he comentado en artículos anteriores, los tests de unidad deben ser independientes y autocontenidos, deberían utilizar la mínima cantidad de infraestructura necesaria para hacer sus pruebas, y deberían estar escritos de forma que sólo fallen o tengan éxito si la unidad que estamos probando falla o funciona correctamente.

Con esto en mente, vamos a escribir unos cuantos tests de unidad para esta clase:

public class WebPageTranslator {
	public String translate(String url, String fromLanguage, String toLanguage) {
		Downloader downloader = new HttpDownloader();
		String page = downloader.download(url);
		if (page == null || fromLanguage.equals(toLanguage)) {
			return page;
		}
		Translator translator = new GoogleTranslator();
		return translator.translate(page, fromLanguage, toLanguage);
	}
}

Como podéis apreciar, vamos a tener un problema bastante gordo a la hora de escribir tests, independientes, autocontenidos y con poca infraestructura. En particular, esta clase depende de HttpDownloader y de GoogleTranslator, que (se supone) siempre descargan contenido de la web y utilizan Google Translate. Esto significa que nuestros tests también dependerán de estas dos clases, por lo que no serán autocontenidos, y podrán fallar si por algún motivo no se puede descargar algo de la web o Google Translate no responde.

Para solucionar este problema debemos rediseñar la clase para que se le puedan inyectar sus dependencias, y utilizar en lugar de HttpDownloader y GoogleTranslator unas clases especiales para pruebas que no necesiten acceder a Internet ni a los servicios de Google:

public class WebPageTranslator {
	private final Downloader downloader;
	private final Translator translator;

	@Inject
	public WebPageTranslator(Downloader downloader, Translator translator) {
		this.downloader = downloader;
		this.translator = translator;
	}

	public String translate(String url, String fromLanguage, String toLanguage) {
		String page = downloader.download(url);
		if (page == null || fromLanguage.equals(toLanguage)) {
			return page;
		}
		return translator.translate(page, fromLanguage, toLanguage);
	}
}

Vamos a escribir ahora unos cuantos tests de unidad, y veremos más adelante cómo serían estas dos clases especiales para tests:

@Test
public void testTranslate() {
	Downloader downloader = new StubDownloader("Valeu!");
	Translator translator = new MockTranslator("Valeu!", "Thank you!",
			"pt", "en");
	WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
			translator);
	assertEquals("Thank you!", pageTranslator.translate(
			"http://example.com/valeu.html", "pt", "en"));
}

@Test
public void doesNotTranslateWhenLanguagesAreEqual() throws Exception {
	Downloader downloader = new StubDownloader("Valeu!");
	Translator translator = new DummyTranslator();
	WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
			translator);
	assertEquals("Valeu!", pageTranslator.translate(
			"http://example.com/valeu.html", "pt", "pt"));
}

@Test
public void returnsNullWhenDownloadFails() throws Exception {
	Downloader downloader = new StubDownloader(null);
	Translator translator = new DummyTranslator();
	WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
			translator);
	assertNull(pageTranslator.translate("http://example.com/valeu.html",
			"pt", "en"));
}

@Test
public void returnsNullWhenTranslatorFails() throws Exception {
	Downloader downloader = new StubDownloader("Valeu!");
	Translator translator = new MockTranslator("Valeu!", null, "pt", "en");
	WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
			translator);
	assertNull(pageTranslator.translate("http://example.com/valeu.html",
			"pt", "en"));
}

Aquí tenemos cuatro tests, que comprueban qué ocurre en el caso normal, cuando el idioma de origen y de destino son el mismo, cuando falla la descarga del texto y cuando falla la llamada al traductor. Para cada uno de estos tests estamos utilizando instancias de StubDownloader, MockTranslator y DummyTranslator, que son clases especiales que hemos creado para los tests, que cumplen las interfaces Downloader y Translator pero que hemos implementado de forma muy simple y están bajo nuestro completo control.

Veamos qué hacen estas tres clases:

La clase StubDownloader devuelve siempre el mismo valor cuando se llama a su método translate(); este valor es el que le hemos pasado en el constructor. En todos los tests este valor es el texto de la página que queremos traducir; en el segundo test, sin embargo, el valor es null para simular un fallo en la descarga.

La clase MockTranslator comprueba que, cuando llamamos al método translate(), le pasamos parámetros con ciertos valores esperados, y si es así, devuelve un resultado predeterminado. Como en el caso anterior, estos valores esperados y resultado predeterminado se le pasan en el constructor.

La clase DummyTranslator no hace nada; simplemente existe para poder inyectar un objeto de tipo Translator en los tests en los que no se realiza ninguna llamada a translate().

Aquí está el código completo de los tests de unidad y de esas tres clases, por si tenéis curiosidad:

public class WebPageTranslatorTest {
	@Test
	public void testTranslate() {
		Downloader downloader = new StubDownloader("Valeu!");
		Translator translator = new MockTranslator("Valeu!", "Thank you!",
				"pt", "en");
		WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
				translator);
		assertEquals("Thank you!", pageTranslator.translate(
				"http://example.com/valeu.html", "pt", "en"));
	}

	@Test
	public void doesNotTranslateWhenLanguagesAreEqual() throws Exception {
		Downloader downloader = new StubDownloader("Valeu!");
		Translator translator = new DummyTranslator();
		WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
				translator);
		assertEquals("Valeu!", pageTranslator.translate(
				"http://example.com/valeu.html", "pt", "pt"));
	}

	@Test
	public void returnsNullWhenDownloadFails() throws Exception {
		Downloader downloader = new StubDownloader(null);
		Translator translator = new DummyTranslator();
		WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
				translator);
		assertNull(pageTranslator.translate("http://example.com/valeu.html",
				"pt", "en"));
	}

	@Test
	public void returnsNullWhenTranslatorFails() throws Exception {
		Downloader downloader = new StubDownloader("Valeu!");
		Translator translator = new MockTranslator("Valeu!", null, "pt", "en");
		WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
				translator);
		assertNull(pageTranslator.translate("http://example.com/valeu.html",
				"pt", "en"));
	}

	private class StubDownloader implements Downloader {
		private final String response;

		public StubDownloader(String response) {
			this.response = response;
		}

		@Override
		public String download(String url) {
			return response;
		}
	}

	private class DummyTranslator implements Translator {
		@Override
		public String translate(String text, String fromLanguage,
				String toLanguage) {
			throw new IllegalStateException();
		}
	}

	private class MockTranslator implements Translator {
		private final String text;
		private final String translation;
		private final String from;
		private final String to;

		public MockTranslator(String text, String translation, String from,
				String to) {
			super();
			this.text = text;
			this.translation = translation;
			this.from = from;
			this.to = to;
		}

		@Override
		public String translate(String text, String from, String to) {
			if (text.equals(this.text) && from.equals(this.from)
					&& to.equals(this.to)) {
				return translation;
			} else {
				throw new IllegalStateException(
						"translate() called with wrong arguments");
			}
		}
	}
}

Las tres clases que describí arriba son tres ejemplos de lo que en inglés llaman “test doubles” (podríamos llamarlas “dobles para pruebas” en español). Los dobles para pruebas son clases que se utilizan en los tests para sustituir a clases que requieren mucha infraestructura, se ejecutan lentamente, son difíciles de utilizar, etc. La gente que trabaja en el asunto suele distinguir cuatro tipos: “dummy”, “stub”, “mock” y “fake”. Los tres primeros tipos los hemos visto en los ejemplos anteriores; el cuarto, “fake”, es una implementación completa del interfaz utilizando tablas hash y otros sistemas para mantener todo en memoria en lugar de usar la red, el disco, la base de datos, etc.

No existen unos criterios bien formados sobre cuándo utilizar uno u otro tipo de doble para pruebas; en general, se utiliza lo que sea más fácil de usar y proporcione unos resultados más fiables. Por ejemplo, los objetos “fake” suelen tener un comportamiento muy similar al del objeto al que sustituyen, pero pueden necesitar mucho código para ponerlos en el estado adecuado para cada test. Los objetos “mock” o “stub” son más fáciles de preparar, pero si la persona que los usa no entiende bien cómo funciona el objeto al que sustituyen, pueden causar falsos positivos o negativos en los tests; además, los tests hechos a base de mocks suelen necesitar muchos cambios si cambia la implementación del objeto al que prueban, lo que no ocurre con tanta frecuencia en los tests hechos a base de fakes.

Otro inconveniente de los mocks es que hace falta escribir mucho código para definirlos; la clase MockTranslator, por ejemplo, tiene 26 líneas y no es particularmente sofisticada porque en cada test sólo se llama a un método una sola vez; imaginad qué pasaría si quisiéseis hacer un objeto mock para sustituir a un PreparedStatement. Sin embargo, este inconveniente se puede obviar utilizando EasyMock, que es una biblioteca que permite crear objetos mock en pocas líneas y con mucha facilidad.

Cuando se utiliza EasyMock sólo hay que crear un objeto mock llamando a una función de EasyMock, luego registrar qué métodos se van a llamar con qué parámetros y qué valor deben devolver, y luego ejecutar el test y comprobar que se hicieron todas las llamadas esperadas.

Como ejemplo, veamos qué aspecto tiene testTranslate() reescrito usando EasyMock:

@Test
public void testTranslate() throws Exception {
	Downloader downloader = EasyMock.createMock(Downloader.class);
	Translator translator = EasyMock.createMock(Translator.class);
	EasyMock.expect(downloader.download("http://example.com/valeu.html"))
			.andReturn("Valeu!");
	EasyMock.expect(translator.translate("Valeu!", "pt", "en")).andReturn(
			"Thank you!");
	EasyMock.replay(downloader, translator);
	WebPageTranslator pageTranslator = new WebPageTranslator(downloader,
			translator);
	assertEquals("Thank you!", pageTranslator.translate(
			"http://example.com/valeu.html", "pt", "en"));
	EasyMock.verify(downloader, translator);
}

En las dos primeras líneas se crean los objetos mock llamando a EasyMock.createMock() para cada interfaz. En las siguientes se le dice a EasyMock que va a haber llamadas a downloader.download() y translator.translate() con ciertos argumentos, y se le dice qué valores tiene que devolver. En la siguiente línea se le dice a EasyMock que ponga a downloader y translator en modo “replay”; a partir de este punto, cada vez que se haga una llamada a un método de cualquiera de estos dos objetos, EasyMock comprobará si era una llamada que esperaba y devolverá el valor indicado si lo era o emitirá una excepción si no lo era. Finalmente, en la última línea, se le dice a EasyMock que verifique si se han realizado todas las llamadas esperadas.

Así, a simple vista, parece que no hemos ganado mucho usando EasyMock, ya que hemos tenido que añadir cinco líneas al test; sin embargo, si reescribimos todos los tests para utilizar EasyMock podremos deshacernos de nuestros tres dobles para pruebas y reducir la cantidad total de código:

public class WebPageTranslatorTest {
	private Downloader downloader;
	private Translator translator;
	private WebPageTranslator pageTranslator;

	@Before
	public void setUp() {
		downloader = EasyMock.createMock(Downloader.class);
		translator = EasyMock.createMock(Translator.class);
		pageTranslator = new WebPageTranslator(downloader, translator);
	}

	@After
	public void tearDown() {
		EasyMock.verify(downloader, translator);
	}

	private void replay() {
		EasyMock.replay(downloader, translator);
	}

	@Test
	public void testTranslate() throws Exception {
		EasyMock.expect(downloader.download("http://example.com/valeu.html"))
				.andReturn("Valeu!");
		EasyMock.expect(translator.translate("Valeu!", "pt", "en")).andReturn(
				"Thank you!");
		replay();
		assertEquals("Thank you!", pageTranslator.translate(
				"http://example.com/valeu.html", "pt", "en"));
	}

	@Test
	public void doesNotTranslateWhenLanguagesAreEqual() throws Exception {
		EasyMock.expect(downloader.download("http://example.com/valeu.html"))
				.andReturn("Valeu!");
		replay();
		assertEquals("Valeu!", pageTranslator.translate(
				"http://example.com/valeu.html", "pt", "pt"));
	}

	@Test
	public void returnsNullWhenDownloadFails() throws Exception {
		EasyMock.expect(downloader.download("http://example.com/valeu.html"))
				.andReturn(null);
		replay();
		assertNull(pageTranslator.translate("http://example.com/valeu.html",
				"pt", "en"));
	}

	@Test
	public void returnsNullWhenTranslatorFails() throws Exception {
		EasyMock.expect(downloader.download("http://example.com/valeu.html"))
				.andReturn("Valeu!");
		EasyMock.expect(translator.translate("Valeu!", "pt", "en")).andReturn(
				null);
		replay();
		assertNull(pageTranslator.translate("http://example.com/valeu.html",
				"pt", "en"));
	}
}

Como podéis ver, es bastante fácil evitar introducir dependencias excesivamente onerosas en vuestros tests, utilizando inyección de dependencias y dobles para pruebas. Además, con EasyMock, podréis crear objetos mock con mucha facilidad, así que no tenéis excusas para no hacerlo :)

En el siguiente artículo veremos cómo se utilizan dobles para pruebas en lenguajes dinámicos, usando Python para los ejemplos. También estamos llegando al fin de la serie, así que si tenéis preguntas o dudas o lo que sea, hacédmelas llegar y trataré de responderlas en uno o más artículos posteriores.

(Primer artículo, siguiente artículo).

Introducción a la inyección de dependencias

Imaginad que estáis trabajando en el software de una tienda online y queréis escribir tests de unidad para el módulo de pagos:

public class PaymentService {
	private final BancoPepePlatform pasarela;

	public PaymentService() {
		this.pasarela = new BancoPepePlatform();
	}

	public String cobrar(Money cantidad, String titular, String numero,
			int caducidadMes, int caducidadAño, int cvv) {
		Result res = pasarela.charge(titular, numero, caducidadMes,
				caducidadAño, cvv, cantidad);
		return res.isSuccess() ? res.getCode() : null;
	};
}

Pronto os encontraréis con un problema bastante gordo: cada vez que se ejecute uno de los tests estaréis comunicándoos con el Banco Pepe. Si la conexión es lenta, los tests de unidad tardarán mucho tiempo en ejecutarse; si el banco cobra por cada transacción, ejecutar los tests saldrá muy caro (literalmente); si en el servidor del banco tienen un bug o la conexión falla, puede que los tests fallen sin que sea culpa vuestra; si alguien se olvida de usar un número de tarjeta de pruebas, alguien se enfadará mucho. Y esto sólo para empezar.

Los tests de unidad deberían ser rápidos, deberían probar cada componente de forma aislada, y deberían depender del mínimo posible de infraestructura para evitar introducir errores no debidos al componente que estamos probando. Estas tres cosas son todo lo contrario de lo que he escrito en el párrafo anterior; por lo tanto, tenemos que encontrar una solución.

La solución pasa por emplear en los tests de unidad un “simulador” de BancoPepePlatform. Este “simulador” puede funcionar de muchas maneras distintas; la idea es que sea rápido, fiable, y que exponga la suficiente funcionalidad para poder utilizarlo en los tests de unidad de PaymentService. El problema que tenemos ahora es hacer que PaymentService use el BancoPepePlatform de verdad o el simulador, dependiendo de si está ejecutándose el código de verdad o los tests de unidad.

Podríamos intentar usar, por ejemplo, una factoría estática que consulte la configuración y devuelva una instancia de uno u otro tipo:

	public class BancoPepePlatformFactory {
		public static BancoPepePlatform get() {
			if (SystemConfiguration.isTestMode()) {
				return new FakeBancoPepePlatform();
			} else {
				return new BancoPepePlatformImpl();
			}
		}
	}

	public PaymentService() {
		this.pasarela = BancoPepePlatformFactory.get();
	}

No obstante, esta solución tiene sus propios problemas: tenemos que acordarnos de activar el “modo test” en todos los tests de unidad, tenemos que incluir FakeBancoPepePlatform en los binarios de producción de la tienda online aunque no vamos a utilizarlo para nada, y si en un test determinado queremos utilizar un simulador distinto, no podemos.

Una mejor solución para esto es inyectar la dependencia. Es decir, PaymentService no crea la instancia de BancoPepePlatform que necesita, sino que se le proporciona una:

	public PaymentService(BancoPepePlatform pasarela) {
		this.pasarela = pasarela;
	}

A partir de este momento, cada vez que creéis una instancia de PaymentService tendréis que crear también una instancia de BancoPepePlatform y pasársela en el constructor. Por ejemplo, de esta manera en vuestro código de producción:

PaymentService service = new PaymentService(new BancoPepePlatformImpl());

Y de esta forma en los tests de unidad:

PaymentService service = new PaymentService(new FakeBancoPepePlatform());

Si en un test necesitáis una instancia “especial” de BancoPepePlatform es trivial proporcionársela:

PaymentService service = new PaymentService(new BancoPepePlatform() {
	@Override
	public Result charge(String titular, String numero,
			int caducidadMes, int caducidadAño, int cvv, Money cantidad) {
		return null;
	}
});

Ahora tendremos que ir hacia “arriba” en la cadena de dependencias y seguir aplicando el patrón, porque si no, seguiremos teniendo el mismo problema de antes. Por ejemplo, veamos PaymentServlet:

public class PaymentServlet extends HttpServlet {
	private final PaymentService paymentService;

	public PaymentServlet(PaymentService paymentService) {
		this.paymentService = paymentService;
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// ...
	}
}

Al final llegaremos a un punto en el que tendremos código que crea toda la cadena de dependencias del programa:

servlets.add(new PaymentServlet(new PaymentService(new BancoPepePlatformImpl())));

Esta cadena de dependencias puede ser bastante difícil de mantener. Por eso, prácticamente todo el mundo utiliza un “framework” de inyección de dependencias como Spring o Guice, que es el que mejor conozco.

Estos frameworks proporcionan un “inyector”, que es una función que proporciona una instancia de la clase solicitada, inyectando todas las dependencias intermedias. Por ejemplo, con Guice podría obtener una instancia de PaymentServlet de esta manera:

PaymentServlet servlet = injector.getInstance(PaymentServlet.class);

Para inyectar correctamente las clases es necesario marcar sus puntos de inyección. En Guice, esto se hace utilizando la anotación @Inject:

@Inject
public PaymentServlet(PaymentService paymentService) {
	this.paymentService = paymentService;
}

@Inject
public PaymentService(BancoPepePlatform pasarela) {
	this.pasarela = pasarela;
}

Lo más habitual es utilizar un constructor o una o más funciones “setter” como puntos de inyección. Sólo puede marcarse un constructor, pero pueden marcarse todas las funciones que se quiera.

Finalmente, el inyector tiene una configuración, que le indica qué clases inyectar. Esto es necesario si en algún punto de inyección se hace referencia a un interfaz; Guice necesita saber qué implementación se debe inyectar para ese interfaz. Por ejemplo, para que Guice inyecte una instancia de BancoPepePlatformImpl para el interfaz BancoPepePlatform:

public class PaymentModule extends AbstractModule {
	@Override
	protected void configure() {
		bind(BancoPepePlatform.class).to(BancoPepePlatformImpl.class);
	}
}

Esta configuración se le pasa a Guice al crear el inyector:

Injector injector = Guice.createInjector(new PaymentModule());

Guice también permite hacer muchas cosas en su configuración. Por ejemplo, se le puede indicar que sólo debería existir una instancia de una clase:

bind(PaymentServlet.class).in(Scopes.SINGLETON);

O que el objeto a inyectar tiene que venir de una factoría:

bind(PaymentService.class).toProvider(new PaymentServiceProvider());

O utilizar anotaciones para hacer distintas inyecciones para la misma interfaz:

bind(BancoPepePlatform.class).annotatedWith(Paypal.class).to(PaypalBancoPepePlatform.class);

@Inject
public PaypalPaymentService(@Paypal BancoPepePlatform platform) {
	this.platform = platform;
}

Una pequeña nota antes de terminar: si usáis la inyección de dependencias correctamente, necesitaréis usar directamente el inyector solamente una vez. Con él obtendréis una instancia de ShopServer, por ejemplo, que tendrá inyectados todos los objetos que necesite; éstos, a su vez, tendrán inyectadas también todas sus dependencias, y así sucesivamente.

Os aconsejo aprender más sobre inyección de dependencias y echarle un vistazo a la documentación de Guice, que explica todo esto muy bien, porque este patrón de diseño y estas herramientas ayudan muchísimo a hacer que vuestras aplicaciones sean modulares y que los tests de unidad sean más fáciles de escribir.

(Primer artículo, siguiente artículo).

Desarrollo dirigido por los tests

Como su propio nombre indica, el desarrollo dirigido por los tests (“test-driven development”, o TDD en siglas) consiste en escribir primero los tests de unidad y luego escribir el código que hace que estos tests pasen con éxito.

Esta técnica tiene varias ventajas. Por ejemplo, el código escrito de esta manera suele exponer interfaces más limpios, fáciles de usar y desacoplados de la implementación que el código escrito de forma normal. Además, el código suele tener menos errores, y menos funcionalidades añadidas de forma especulativa.

Permitidme explicar este último punto, ya que es menos obvio que los anteriores. Muchas veces, cuando escribimos código, solemos añadir cosas que no necesitamos inmediatamente, pero que suponemos que vendrán bien en el futuro, cuando tengamos que hacer escalar la aplicación o cuando tengamos que añadir nuevas funciones o cuando tengamos que sustituir la base de datos. El problema con este código extra es que... bueno, es código extra: más código que hemos de mantener, más sitios donde pueden esconderse bugs, más espacio que ocupa el programa, más despacio que el programa va, etc. Por lo tanto, es recomendable evitar añadir código de forma especulativa y dejarlo para cuando realmente vayamos a necesitarlo; por desgracia, es una tentación muy difícil de evitar. Sin embargo, al hacer TDD es más fácil concentrarse en escribir sólo el código que hace que los tests pasen ahora mismo y no caer en la tentación.

Por supuesto, el TDD tiene inconvenientes además de ventajas. Por ejemplo, en ocasiones es muy difícil escribir tests para un código todavía inexistente. Otro inconveniente que podéis encontraros es que alguna gente se pasa de lista y cae en la tentación de escribir código que sólo funciona para los tests, en lugar de escribir código que pasa los tests porque es correcto. Si os encontráis con uno de estos, tenéis mi permiso para darles una bofetada (pero no digáis que fui yo quien os lo dio).

En algunos sitios llevan esta técnica un poco más lejos y hacen que una persona escriba los tests y luego otra persona distinta escriba el código. De este modo consiguen que dos personas conozcan el código en lugar de una sola y hacen que sea más difícil introducir bugs (es más difícil que dos personas introduzcan errores que se neutralizan mutuamente que que lo haga una persona sola).

Vamos a ver un pequeño ejemplo de TDD, escribiendo una pequeña implementación de un conjunto en Java. Vamos a comenzar creando la clase MySetTest, donde escribiremos los tests de unidad. También creamos la clase MySet, pero sólo pondremos en ella el esqueleto; sólo lo necesario para que Eclipse no se queje y para poder compilar y ejecutar los tests.

public class MySetTest {
	private MySet<Object> set;
	
	@Before
	public void setUp() {
		set = new MySet<Object>();
	}
	
	@Test
	public void newSetIsEmpty() throws Exception {
		assertEquals(0, set.size());
	}
}

public class MySet<E> {
	public int size() {
		return 0;
	}
}

Como podéis ver, he escrito ya un test de unidad para comprobar que un nuevo conjunto está vacío, y he añadido en MySet el correspondiente esqueleto para el método size(). Vamos a añadir unos pocos tests más:

public class MySetTest {
	private static final Object OBJ1 = new Object();
	private static final Object OBJ2 = new Object();
	
	private MySet<Object> set;
	
	@Before
	public void setUp() {
		set = new MySet<Object>();
	}
	
	@Test
	public void newSetIsEmpty() throws Exception {
		assertEquals(0, set.size());
	}
	
	@Test
	public void addElementIncreasesSizeIfElementIsNew() throws Exception {
		set.add(OBJ1);
		assertEquals(1, set.size());
		set.add(OBJ1);
		assertEquals(1, set.size());
		set.add(OBJ2);
		assertEquals(2, set.size());
	}

	@Test
	public void onlyContainsAddedElements() throws Exception {
		assertFalse(set.contains(OBJ1));
		assertFalse(set.contains(OBJ2));
		set.add(OBJ1);
		assertTrue(set.contains(OBJ1));
		assertFalse(set.contains(OBJ2));
	}
}

public class MySet<E> {
	public int size() {
		return 0;
	}

	public void add(E e) {
	}

	public boolean contains(E e) {
		return false;
	}
}

Nuevamente, fijaos en que mi implementación de MySet sólo contiene lo necesario para que los tests compilen y Eclipse no me llene la pantalla de líneas rojas.

Ahora podemos ejecutar los tests, y veremos que algunos pasarán y otros (la mayoría) fallarán. Nuestra tarea ahora consiste en rellenar el esqueleto de MySet con el código necesario para hacer que los tests pasen. Por ejemplo:

public class MySet<E> {
	private List<E> elems;
	
	public MySet() {
		elems = new ArrayList<E>();
	}
	
	public int size() {
		return elems.size();
	}

	public void add(E e) {
		if (!elems.contains(e)) {
			elems.add(e);
		}
	}

	public boolean contains(E e) {
		return elems.contains(e);
	}
}

Ahora, con sólo ejecutar los tests, sabemos que este código funciona correctamente. Por supuesto, este ejemplo es muy simple, pero imaginad las ventajas que os proporcionaría a la hora de escribir un código más complicado.

Como ejercicio, podéis probar a escribir la función para eliminar un elemento del conjunto: primero escribid un test que compruebe que al eliminar un elemento, el tamaño del conjunto disminuye en 1 si éste estaba en el conjunto, y otro que compruebe que un conjunto no contiene un elemento eliminado. Después, escribid el código que haga que los tests pasen.

La técnica del TDD también es muy útil a la hora de corregir bugs. Para aplicarla, primero escribís un test que falle si el bug existe, y luego modificáis el código para que pase este test. Al hacerlo así, os lleváis dos cosas de regalito: sabéis que vuestro arreglo no afecta al resto de funcionalidades, y sabéis que este bug no reaparecerá en el futuro, ya que ahora tenéis un test que lo detecta.

Hasta ahora hemos visto cómo escribir tests para unidades más bien simples. Por desgracia, casi siempre tenemos que tratar con componentes que dependen de otros componentes, y otras cosas que hacen que escribir tests de unidad pueda ser muy engorroso. En las próximas entregas os explicaré cómo organizar vuestro código para aislar estas dependencias y facilitar la escritura de los tests de unidad.

(Primer artículo, siguiente artículo).