Haz una librería de la que me pueda fiar… o no la hagas

Cuando creas código, debes poder confiar en algunas cosas, como el sistema operativo o el intérprete de Python. Si no, sería casi imposible encontrar un error. Por suerte casi todo lo que usamos es lo suficientemente maduro para tener la certeza de que si algo no funciona la causa está en nuestro código.

¿Qué pasa con las librerías de terceros? También tenemos que confiar en ellas y para ello hay que comprobar los parámetros de entrada. Esta entrada te propone que hagamos juntos una reflexión sobre este tema.

Verificando parámetros

Una librería es un fragmento de código muy delicado. No está pensada para ser utilizada por un usuario final a través de una interfaz que limite y controle lo que el usuario pueda hacer. Una librería está pensada para ser parte de otro código y para permitir que sean programadores o software craftmen quienes la utilicen a veces de manera muy ingeniosa.

Para que una librería tenga éxito y los desarrolladores la utilicen es fundamental que sea de fiar. Una librería podrá hacer su trabajo mejor o peor, incluso puede no ser capaz de trabajar en determinadas circunstancias, pero si obtenemos un error de nuestro código siempre debe estar claro si el error es interno de la propia librería y hemos encontrado un bug o nos topamos con una limitación documentada, si el error es de nuestro código que usa mal la librería o si el error es de nuestro código y no tiene nada que ver con la librería.

Para conseguir esto es fundamental tratar los parámetros que reciben los métodos o funciones de la librería adecuadamente. Tomemos como ejemplo una sencilla librerías de funciones matemáticas básicas: potencia, exponencial, divisores, mínimo común divisor, máximo común múltiplo, etc.

Algunas de estas funciones no aceptan números negativos,  ¿tenemos que comprobar todos los métodos? .

Si un parámetro negativo producirá una excepción más adelante, ¿para qué esperar?. Una buena alternativa es comprobar el parámetro justo al comienzo de la llama al método o función y lanzar una excepción. Por ejemplo, imagina esta implementación básica de la función de Fibonacci.

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

¿Cuál es la diferencia entre verificar el valor del parámetro o dejar que el código falle dónde tenga que fallar? Compruébalo tú mismo viendo cuál de los dos siguientes errores es más claro.

Sin comprobar valores de entrada.

Error en función de Fibonacci sin comprobar parámetros

Comprobando valores de entrada.

res02

Si comprobamos todos los parámetros de entrada, estaremos escribiendo una librería sólida que ayuda a sus usuarios a escribir buen código. Pero también podemos estar repitiendo muchas veces el código de comprobación y repetir código siempre es malo.

Por suerte tenemos varias alternativas. Además, resolver este problema nos da una indicación de nuestro nivel de programación. Algunas opciones son:

  • Creamos un método interno auxiliar que compruebe los parámetros. Todos nuestros métodos llamarían a dicho método (así está implementada las clases de java.collection, por ejemplo).
  • Creamos un decorador que compruebe los parámetros (los decoradores no son tan difíciles como parece).
  • Utilizamos alguna librería de comparaciones, como sure o expects.

Si se te ocurren más soluciones ponlas como comentario de este artículo. No te preocupes si crees que no son mejores que las que ya hemos visto, porque siempre nos servirá para aprender un poco más.

Unidades con magnitudes

Otro tema interesante es cómo trabajar con magnitudes que tienen una unidad asociada. Por ejemplo, pensemos en la temperatura. Si nos dan estas temperaturas: 10, 283 y 50, ya podemos cruzar los dedos para que el termómetro se haya estropeado.

Pero si nos dicen que la temperatura es 10 grados centígrados, 283 grados kelvin y 50 grados Fahrenheit descubrimos que, en verdad, estamos hablando de la misma temperatura y que mejor vamos abrigados o pasaremos frio.

A Python y a nuestras librerías le pasa lo mismo, a veces necesita las unidades porque un un número no les dice nada. Pero Python no tiene ningún tipo de dato nativo para empresar magnitudes y unidades. Lo mejor es crearlo nosotros mismos con una clase. ¿Cómo podría ser una clase temperara (llamada temp para abreviar)?

  • Podríamos crear métodos estáticos, por ejemplo  tem.kelvin(50) o temp.celcius(-4) que comprobaran que una temperatura es válida en el rango de sus unidades.
  • Podríamos crear métodos de conversión para obtener la temperatura en la magnitud que deseemos, por ejemplo si creamos temp.kelvin(283) y vemos el valor de temp. celcius () obtenemos un correcto 10.
  • Podríamos sobrecargar los operadores para comparar y operar con temperaturas más intuitivamente.
  • Y podríamos buscar si alguien ya lo ha hecho por nosotros.

Nos evitaríamos, por ejemplo el problema de mezclar distintas magnitudes, ya que la clase  verificaría por nosotros que las operaciones se realicen siempre con la misma unidad.

Bola extra

Existe una técnica de diseño 7 programación conocida como diseño por contratos (design by contract) en las que, para cada operación se especifican sus precondiciones, post-condiciones e invariantes y luego se implementan en el código.

Esta técnica alcanzó popularidad a finales de los 90 por el desastre el cohete Ariane 5, el cual se podría haber evitado especificando los contratos de las operaciones.

Ariadne 5, o lo que queda.
Ariane 5, o lo que queda.

Si os manejáis por el mundo Java, podéis ver esta técnica en aplicación por ejemplo en java.collection, aunque luego no implementéis todas las precondiciones, poscondiciones e invariante sí es una buena idea pensar en los contratos de vuestras librerías. Además, también sirve de ayuda para diseñar casos de prueba.

Desarrollo Dirigido por Pruebas en Python (III). Independencia del Sistema

En esta entrega continuamos aplicando desarrollo dirigido por pruebas (o Test-Driven Development en inglés) para implementar una aplicación que busque tweets con un hashtag concreto y los guarde en un archivo. En concreto nos centraremos en el código que construye el nombre del fichero con la fecha actual y el hashtag buscado.
Nos enfrentaremos con el problema de depender de la fecha del sistema por lo que el resultado esperado de la pruebas cambiará cada día. Para solucionarlo, veremos cómo podemos independizar nuestro código de detalles del sistema y hacerlo más fácil de probar. Esto nos dará pie a presentar los dobles de prueba.
Las entregas anteriores de esta serie son:

Desarrollo dirigido por pruebas en Python (I): Una historia que pasa todos los días

Desarrollo Dirigido por Pruebas en Python (II). Un Caso Práctico (I)

Aunque la aplicación es la misma en todas las entregas, en cada una nos centramos en un problema concreto, así que puedes leer esta entrega aunque no hayas leído las dos anteriores.

El diario de diseño

Vamos a recuperar nuestro diario de diseño de la entrega anterior para ver en qué estado se encuentra el desarrollo de nuestra aplicación.

Diario de diseño.
1) Conectarse a Internet y obtener los mensajes que contengan un tag concreto.2) Procesar los tweets para obtener el nombre y dirección twitter del autor, la fecha y el contenido del tweet.

2.1) Definir el contenido de tweetDePrueba

2.2) Los elementos de la lista resultante deben tener el nombre y dirección twitter del autor, la fecha y el contenido del tweet.

3) Guardar la información anterior en un nuevo fichero

4) Crear una interfaz de usuario por línea de comandos.

Ya hemos terminado la funcionalidad 2. Ahora podemos elegir la tarea que quieras implementar. Para esta entrada, vamos a elegir la tarea 3. Al igual que hicimos en la entrega anterior, vamos a descomponer esta área en otras más pequeñas:

Diario de diseño.
1) Conectarse a Internet y obtener los mensajes que contengan un tag concreto.2) Procesar los tweets para obtener el nombre y dirección twitter del autor, la fecha y el contenido del tweet.3) Guardarlos tweets procesados en un nuevo fichero

3.1) Crear el nombre de fichero siguiendo este formato: “año-mes-dia tag.txt”

3.2) Cada tweet se guarda en una línea con los valores separados por comas.

3.3) Crear el fichero con el nombre y contenido indicados en 3.1 y 3.2

4) Crear una interfaz de usuario por línea de comandos.

Como se describe en el diario de diseño, vamos a crear un fichero cuyo nombre contenga la fecha del sistema. A continuación empezamos a implementar nuestra nueva funcionalidad escribiendo pruebas.

La primera prueba

Una vez qué tenemos claro qué vamos a hacer, el primer paso es escribir una prueba para expresar el resultado esperado del código que vamos a escribir. Empezamos generando el nombre adecuado de un fichero y, para ello, escribiremos una prueba que exponga cómo lo queremos obtener.

class TestTweetsEnFichero(unittest.TestCase):
	def testCreaNombreFichero(self):
		tef = TweetsEnFichero()
		esperado = "2013-03-30 PyConEs.txt"
		self.assertEquals(esperado, tef.crearNombreFichero("PyConEs"))

Para que esta prueba pase, creamos una nueva clase, TweetsEnFichero, que contendrá todo lo necesario para almacenar el contenido de los tweets en un fichero. Suponemos que escribimos y ejecutamos esta prueba el 30/03/2013. La implementación más sencilla para que esta prueba pase es:

class TweetsEnFichero:
	def crearNombreFichero(self, tag):
		return  "2013-03-30 PyConEs.txt"

Con esta implementación tenemos el código más sencillo que hace que la prueba pase con éxito. Ahora que ya tenemos algo que funciona y que nos muestra cómo trabajar vamos a mejorarlo. Podemos hacer algunas refactorizaciones al código anterior. Por ejemplo, ya que tenemos el hashtag, podemos utilizarlo y comprobar que la prueba sigue funcionando.

class TweetsEnFichero:
	def crearNombreFichero(self, tag):
		return  "2013-03-30 “+tag+”.txt"

Veamos a continuación cómo podemos independizarnos de la fecha actual.

¡Independencia!... del día y la hora                     

Comenzamos separando la responsabilidad de obtener la fecha darle el formato adecuado del resto del código. Para ello vamos a escribir un nuevo método y nuestro primer paso, lógicamente, será escribir primero la prueba que nos defina qué debe hacer el código que vamos a escribir.

def testGetFechaActual(self):
		tef = TweetsEnFichero()
		esperado = "2013-03-30"
		self.assertEquals(esperado, tef.getFechaActual())

Una vez que la prueba falla pasamos a la implementación. Veamos el código.

def getFechaActual(self):
		now = datetime.now()
		return str(now.year) + "-" + str(now.month) + "-" + str(now.day)

Sin embargo con la implementación anterior la prueba falla porque el mes es 3 en vez de 03. Esto también pasará cuando el día sea menor de 10. Como esta prueba no pasa con éxito no podemos dar por terminada la implementación. La solución más rápida es añadir el “0” que falta, como se muestra en el siguiente código.

def getFechaActual(self):
		now = datetime.now()
		return str(now.year) + "-" + “0” + str(now.month) + "-" + str(now.day)

Ahora la prueba pasa con éxito. Además, hemos descubierto una nueva tarea que necesitamos para que el nombre se genere correctamente, y que añadimos a nuestro diario (tarea 3.4).

Diario de diseño.
1) Conectarse a Internet y obtener los mensajes que contengan un tag concreto.2) Procesar los tweets para obtener el nombre y dirección twitter del autor, la fecha y el contenido del tweet.3) Guardarlos tweets procesados en un nuevo fichero3.1) Crear el nombre de fichero siguiendo este formato: “año-mes-día tag.txt”

3.2) Cada tweet se guarda en una línea con los valores separados por comas.

3.3) Crear el fichero con el nombre y contenido indicados en 3.1 y 3.2

3.4) Añadir un “0” cuando el mes o el día sean menores de 10.

4) Crear una interfaz de usuario por línea de comandos.

Este es un buen momento para refactorizar. Vamos a refactorizar las pruebas para evitar código duplicado. En concreto sacamos la creación del objeto bajo prueba al método setUp tal y como se muestra a continuación.

class TestTweetsEnFichero(unittest.TestCase):
	def setUp(self):
		self.tef = TweetsEnFichero()
	def testCreaNombreFichero(self):
		esperado = "2013-03-30 PyConEs.txt"
		self.assertEquals(esperado, self.tef.crearNombreFichero("PyConEs"))
	def testGetFechaActual(self):
		esperado = "2013-03-30"
		self.assertEquals(esperado, self.tef.getFechaActual())

También refactorizamos el código de la aplicación para que utilice el método getFechaActual en vez de la fecha incrustada como cadena de texto y el parámetro hashtag.

class TweetsEnFichero:
	def crearNombreFichero(self, hashtag):
		return self.getFechaActual() +" "+ hashtag + ".txt"

Podríamos continuar escribiendo pruebas para que el código añada un cero delante del mes y del día cuando haga falta, pero nuestro código aún depende de la fecha actual del sistema (llamada a datetime::now) lo que aumenta la dificultad de implementar esta funcionalidad. Por ello, nuestro siguiente paso será independizarnos de la fecha del sistema.

Funciona hoy, funciona mañana

Las pruebas anteriores solo funcionarán hoy, mañana será otro día, datetime::now devolverá otra fecha y las pruebas fallarán. El problema es que hemos incrustado una dependencia con datetime::now en nuestro código, por lo que depende de la llamada al sistema que devuelve la fecha. Necesitamos una manera de romper esa dependencia para poder controlar el código.

El primer paso es refactorizar para proporcionar el datetime a utilizar desde el exterior. El código resultante se muestra a continuación.

class TweetsEnFichero:
	def __init__(self, timeProvider):
		self.timeProvider = timeProvider
	def getFechaActual(self):
		now = self.timeProvider.now()
		return str(now.year) + "-" + str(now.month) + "-" + str(now.day)

Después, modificamos las pruebas que tenemos que usen datetime y todo sigue funcionando. Como al final de la sección anterior refactorizamos el código para evitar el código duplicado, solo hemos tenido que modificar una única línea. Si no hubiéramos refactorizado tendríamos que cambiar todas las pruebas. Recuerda, evitar el código duplicado hace tu código más sencillo de cambiar y mejorar.

class TestTweetsEnFichero(unittest.TestCase):
	def setUp(self):
		self.tef = TweetsEnFichero(datetime)

;

Vamos a continuar modificando las pruebas. Como podemos indicar desde el exterior el encargado de devolver la fecha, podemos crear un “doble” que devuelva siempre la misma fecha. Así podremos saber cuál serán los resultados esperados de la pruebas. Nuestro doble de pruebas se llama DatetimeStub y su misión es reemplazar al Datetime del sistema para devolver siempre la misma fecha. Así, podemos predecir los resultados esperados de la prueba y hacer que no cambien de un día para otro.

class DatetimeStub ():
	def now(self):
		self.year = 2013
		self.month = 3
		self.day = 29
		return self

Los dobles de prueba son un elemento muy poderoso a la hora de escribir pruebas. Al final de este artículo tienes más información sobre dobles. Vamos a modificar la prueba que hemos escrito para utilizar nuestro doble.

class TestTweetsEnFichero(unittest.TestCase):
	def setUp(self):
		self.tef = TweetsEnFichero(DatetimeStub ())

Todas las pruebas siguen funcionando correctamente. Ya estamos independizados de la fecha y hora.

Hazlo pero no me digas cómo

Por comodidad, podemos hacer que, si no se indica ningún datetime concreto, se utilice el datetime del sistema. Vamos a escribir una prueba que lo ponga de manifiesto:

def testGetFechaActual_DatetimePorDefecto(self):
		self.tef = TweetsEnFichero()
		self.assertEquals(datetime, self.tef.timeProvider)

Y ahora implementamos el datetime del sistema por defecto en el constructor  lo utilizamos en el método getFechaActual.

class TweetsEnFichero:
	def __init__(self, timeProvider = datetime):
		self.timeProvider = timeProvider
	def getFechaActual(self):
		now = self.timeProvider.now()
		return str(now.year) + "-" + "0" + str(now.month) + "-" + str(now.day)

Una vez que todo funciona, continuamos con la siguiente funcionalidad.

Un cero a la izquierda

Tenemos pendiente en nuestro diario de diseño añadir un “0” cuando el mes y día sean menores de 10. Vamos a escribir nuevas pruebas que nos empujen a implementar esta funcionalidad, por ejemplo la siguiente:

def testGetFechaActual_ConMesMayorQue10NoSeIncluyeElCero(self):
		self.tef = TweetsEnFichero(DatetimeStub("12-10-2013"))
		esperado = "2013-10-12"
		self.assertEquals(esperado, self.tef.getFechaActual())

Como se ve en la prueba anterior, necesitamos usar otras fechas distintas que la incluida en nuestro doble de pruebas (clase DatetimeStub). Vamos a modificarlo para que pueda aceptar cualquier fecha como se muestra a continuación.

class DatetimeStub():
	def __init__(self, date = "30-3-2013"):
		fields = date.split("-")
		self.year = int(fields[2])
		self.month = int(fields[1])
		self.day = int(fields[0])
	def now(self):
		return self

Recuerda que el código de prueba (casos de prueba, dobles, etc.) debe ser lo más sencillo posible por dos motivos. El primero es que no escribimos pruebas para verificar pruebas (ni dobles) por lo que tienen que ser simples y de pocas líneas para evitar fallos. El segundo es que, cuando haya un error en el código, una o varias pruebas fallarán. Queremos entender rápidamente qué hace la prueba para que nos dirijan hacia el error en el código.

Una vez que hemos modificado DatetimeStub y ejecutamos la prueba esta falla, ya que siempre añadimos un “0” delante del mes. Añadimos un if al método getFechaActual y la prueba funciona (veremos este código un poco más adelante).

Vamos a escribir una segunda prueba que ponga un 0 delante del día. Esta prueba se muestra a continuación.

def testGetFechaActual_ConDiaMenorQue10SeIncluyeUnCero(self):
		self.tef = TweetsEnFichero(DatetimeStub("7-10-2013"))
		esperado = "2013-10-07"
		self.assertEquals(esperado, self.tef.getFechaActual())

La prueba falla así que podemos añadir más código. Añadimos otro if para controlar si le ponemos el 0 al día y, con eso, todas las pruebas funcionan ya. El método getFechaActual (con el cambio de añadir un 0 al mes y al día) ha quedado así.

def getFechaActual(self):
		now = self.timeProvider.now()
		month = str(now.month)
		if now.month < 10:
			month = "0" + month
		day = str(now.day)
		if now.day < 10:
			day = "0" + day
		return str(now.year) + "-" + month  + "-" + day

Toca refactorizar. Vemos que el código para añadir un 0 es el mismo y que solo cambia el valor (mes o día). Para evitar duplicar el código extraemos un método auxiliar, al que llamamos cadenaDosDigitos, tal y como se muestra a continuación.

def cadenaDosDigitos(self, num):
		if num < 10:
			return "0" + str(num)
		return  str(num)
	def getFechaActual(self):
		now = self.timeProvider.now()
		return str(now.year)
			   + "-" + self.cadenaDosDigitos(now.month)
			   + "-" + self.cadenaDosDigitos(now.day)

Todas las pruebas funcionan por lo que damos por cerrada esta funcionalidad. Repasemos el diario de diseño.

Diario de diseño.
1) Conectarse a Internet y obtener los mensajes que contengan un tag concreto.2) Procesar los tweets para obtener el nombre y dirección twitter del autor, la fecha y el contenido del tweet.3) Guardarlos tweets procesados en un nuevo fichero

3.1) Crear el nombre de fichero siguiendo este formato: “año-mes-día tag.txt”

3.2) Cada tweet se guarda en una línea con los valores separados por comas.

3.3) Crear el fichero con el nombre y contenido indicados en 3.1 y 3.2

3.4) Añadir un “0” cuando el mes o el día sean menores de 10.

4) Crear una interfaz de usuario por línea de comandos.

Hasta aquí llegamos en esta entrada. En la siguiente escribiremos el código que guarde los tweets en el fichero, ya con el nombre correcto.

Dobles de prueba

Para escribir buenas pruebas unitarias rompemos las dependencias de la clase bajo prueba. Así, centramos la prueba en un único fragmento de código y podemos controlar todas sus interacciones con el entorno, como por ejemplo, con la fecha del sistema. Sabremos que, si la prueba falla, el fallo estará en el código bajo prueba, no en el código de los métodos auxiliares. El diseño queda más flexible ya que no incrustamos dependencias con otras clases dentro de nuestro código.

Para conseguir esta independencia, podemos utilizar versiones ficticias de las clases colaboradoras que devuelvan valores predecibles. Con ello podremos definir los resultados esperados para la prueba.  Estas clases ficticias son dobles de prueba (o test doubles en inglés). A veces también se llaman  mocks, fakes, spies pero en la actualidad dichos nombres se utilizan para indicar dobles de prueba con algunas características especiales.

Antes hemos creado nuestro propio doble de prueba (la clase DatetimeStub), pero existen herramientas que nos facilitan la creación e dobles. Para saber más sobre dobles de prueba consulta la sección logro desbloqueado un poco más adelante.

Conclusiones

En esta segunda entrega nos hemos encontrado el problema de depender de una característica del sistema que no podíamos controlar, la fecha, y cómo hemos evolucionado nuestro diseño. El código resultante que hemos creado en esta entrada se muestra a continuación

class TweetsEnFichero:
	def __init__(self, timeProvider = datetime):
		self.timeProvider = timeProvider
	def _getFechaActual(self):
		now = self.timeProvider.now()
		return str(now.year)
			+ "-" + self._cadenaDosDigitos(now.month)
			+ "-" + self._cadenaDosDigitos(now.day)
	def _cadenaDosDigitos(self, num):
		pre = ""
		if num < 10:
			pre = "0"
		return  pre+ str(num)
	def crearNombreFichero(self, hashtag):
		return self._getFechaActual() +" "+ hashtag + ".txt"

Test-Driven Development nos ha ayudado a llegar hasta un mejor diseño al detectar la necesidad de extraer la dependencia del datetime del sistema para poder escribir pruebas. Nos vemos en la próxima entrada.

Logro desbloqueado

En este ejemplo hemos creado nuestros propios dobles de prueba, pero existen varias librerías que automatizan este trabajo por nosotros. En Python, a partir de la versión 3.3, incorpora una librería de mocks llamada MagicMocks y que también se puede utilizar de manera independiente. Utilizando esta librería, nuestro ejemplo quedaría de la siguiente manera.

def testGetFechaActual_ConMesMayorQue10NoSeIncluyeElCero(self):
        datetimeStub = Mock()
        datetimeStub.year = 2013
        datetimeStub.month = 3
        datetimeStub.day = 30
        datetimeStub.now = Mock(return_value=datetimeStub)
        tef = TweetsEnFichero(datetimeStub)
           esperado = "2013-03-30"
        self.assertEquals(esperado, tef.getFechaActual())

Y ya no es necesario crear la clase DatetimeStub. Puedes leer más sobre dobles de prueba y Python en el blog del autor: Un ejemplo de Mocks en Python (con Mockito y MagicMock).  También puedes encontrar más teoría (muy resumida y con ejemplos en Java) sobre el mundo de los dobles de prueba en el libro inconcluso Desarrollo Dirigido por Pruebas Práctico. Por último, tienes otro ejemplo de cómo aislarte de la dependencia de la fecha del sistema en Python aquí: Cómo aislarnos de las dependencias del sistema. Un caso práctico con Python, MagicMock y TDD

Desarrollo Dirigido por Pruebas en Python (II). Un Caso Práctico (I)

A principios de año escribimos una entrada que puedes leer aquí. Después de un parón más largo de lo previsto, volvemos a la carga con el desarrollo dirigido por pruebas en Python.

Vamos a utilizar TDD y el módulo unittest para crear una aplicación que se conecte a twitter, recupere los mensajes de un hashtag y almacene parte de los mensajes en un fichero. El nombre del fichero será la fecha actual y el hashtag. El fichero contendrá la información de cada tweet separada por comas.

Dividiremos este ejemplo en varias entradas. En cada entrada, desarrollaremos una parte de la aplicación a medida que aprendemos distintas técnicas y buenas prácticas para aplicar TDD. En la primera entrada empezaremos con la aplicación y veremos algunos aspectos clave de la aplicación de TDD.

El diario de diseño

Vamos  comenzar creando el diario de diseño. Este diario es la lista de tareas que tenemos pendientes (una to-do list) de hacer cómo código a escribir o funcionalidad a implementar y nos va a servir para mantenernos en todo momento con el foco centrado. Utilizamos el diario cuando ya tengamos una funcionalidad implementada (y gracias a TDD probada) para decir la siguiente y cuando, durante un ciclo de TDD, se nos ocurra nueva funcionalidad o nuevas condiciones a implementar en nuestro código. Vamos a ver las tareas que tenemos que hacer en el cuadro 1.

Cuadro 1. Diario de diseño.
1) Conectarse a Internet y obtener los mensajes que contengan un tag concreto.
2) Procesar los tweets para obtener el nombre y dirección twitter del autor, la fecha y el contenido del tweet.
3) Guardar la información anterior en un nuevo fichero.
4) Crear una interfaz de usuario por línea de comandos.

Para empezar a escribir código elegimos una de las tareas anteriores, la que queramos. No tenemos que imponernos un orden ni pensar que tenemos dependencias. En su lugar elegimos la tarea que consideremos más importante, más arriesgada, más rápida de implementar, etc.

Continue reading

Desarrollo dirigido por pruebas en Python (I): Una historia que pasa todos los días

Vamos a iniciar una serie de artículos sobre desarrollo dirigido por pruebas en Python (TDD en inglés) con el objetivo de acercarlo a científicos e ingenieros. En el primero presentaremos la idea principal del desarrollo dirigido por pruebas, y para ello empezamos una pequeña historia:

Una empresa de desarrollo de productor de jardinería a medida, GardenTech, tiene un nuevo cliente, el señor Sellers. La reunión de requisitos podría ser algo así:

Ingeniero GardenTech: Buenos días señor Seller, díganos qué es lo que necesita.

Sellers: Necesito una manera de poder regar mis plantas.

GT: Podemos ayudarle, tenemos mucha experiencia en ese campo. ¿En qué ha pensado?

S: Tengo 5 macetas, así que me gustaría llevar el agua para allá y echársela.

GT: Perfecto, le pondremos a su aparato un agujero grande para que pueda llenarlo de agua y muchos pequeñitos para que no tronche las flores.

S: Pero ¿y si se me cae?

GT: Tranquilo, la usabilidad es nuestra especialidad, le añadiremos un asa para que pueda manejarlo y no será muy grande para que no pese.

S: ¿no será grande? Entonces igual lo pierdo.

GT: Todo está pensado, le daremos un color rojo brillante para que pueda encontrarlo a simple vista.

S: ¿Y si me mojo?

GT: Los agujeros pequeños estarán alejados del dispositivo mediante un tubo.

S: Perfecto, veo que piensan en todo.

GT: Somos buenos.

El señor Sellers y GardenTech están de acuerdo  en los requisitos que debe tener el artefacto, y GardenTech comienza a desarrollarlo. Un mes después la empresa llama al seños Sellers. En medio de la sala de reuniones hay una mesa con un bulto cubierto por una sábana.

Continue reading