Tengo que confesar que tengo un problema: no respeto a la gente que trabaja mucho. Bueno, mejor dicho, a la gente que está siempre haciendo cosas. Bueno, tampoco se trata de respeto propiamente dicho, más bien de falta de entendimiento. Sí, creo que eso es más bien lo que me pasa: no entiendo a la gente que está todo el día ocupada haciendo cosas. Ahora está mejor (perdón a los que se hayan podido sentir ofendidos).


Profundizando un poco más en el tema, la gente que se empeña en rellenar cada espacio libre que les queda en la agenda para hacer todavía un poco más de no sé muy bien qué realmente me desconcierta. En mi mente, eso se corresponde con un esquema de pensamiento industrial que considera que lo más costoso es tener una máquina parada así que asegurémonos de que las máquinas estén siempre funcionando, haciendo algo, si es preciso organizando turnos, para que no paren nunca de producir.

Pero mi visión es diametralmente opuesta a ésa. Yo no veo ninguna maquinaria súper cara que haya que mantener todo el día en funcionamiento para que sea rentable y, por lo tanto, el tiempo ocioso no implica para mí una pérdida de dinero (ni de productividad) sino más bien al contrario. Para mí, el tiempo desocupado significa otra cosa bien distinta: significa oportunidad. ¿Oportunidad para qué? Para hacer algo que marque la diferencia, algo único, original, que sobresalga, y hacerlo realmente bien. Sinceramente, no me imagino haciendo algo de verdadero impacto si me paso veinticinco horas al día atareado con mil cosas. Y no quiero decir que sólo quiera dedicarme a hacer las partes más bonitas, chulas e interesantes de un proyecto o empresa en la que nos hayamos embarcado ni que le tenga tirria al trabajo duro y la dedicación a los detalles más oscuros y desagradables. Ciertamente creo en el valor del trabajo duro por encima de todo. Una vez que tienes definida una meta y sabes adónde quieres llegar, lo que debes hacer es trabajar duro hasta conseguirlo, tanto como sea necesario.

Pero también creo que hay que tener tiempo y cierto espacio mental para poder estar atento a la próxima oportunidad que se presente. Porque se van a presentar oportunidades, y muchas veces no somos capaces de verlas porque estamos ocupados haciendo otras cosas.

Creo que, en definitiva, el problema es que existe cierta confusión entre:

  • Ser eficaz
  • Ser eficiente
  • Estar ocupado
Y el orden en que he escrito estas cosas no es casual: mi principal objetivo es siempre ser eficaz (porque quiero conseguir cosas). Si puedo conseguirlas empleando menos recursos, es decir, si puedo ser eficiente, estaré más contento. Pero eso es opcional, un segundo paso. Porque si he exprimido al máximo mis recursos para no conseguir lo que quería (o, peor aún, para conseguir algo que no es lo que quería), entonces no voy a estar nada contento. Y, por supuesto, lo último que quiero es estar ocupado.
Muchos confunden eficacia y eficiencia, y creen que ésta significa estar siempre ocupados
Estar ocupado es una consecuencia de los objetivos que quiero conseguir y los medios que tengo a mi disposicion. Quizá estoy intentando conseguir algo muy difícil o muchas cosas al mismo tiempo (cuidado, la pérdida de foco es otro problema importante, pero de ese tema podemos hablar otro día) o quizá no dispongo de todos los recursos que quisiera para poder alcanzar mis objetivos. Entonces estaré muy ocupado trabajando para conseguir lo que quiero. Pero, por lo general, ése no es un estado en el que me quiera encontrar, y mucho menos todos los días de mi vida.

Desgraciadamente muchos confunden la eficacia con la eficiencia, priorizando esta última, que, además, creen que significa estar siempre ocupados. Es una forma de conseguir cierta paz mental al poder decirnos por la noche: "Hemos estado todo el día trabajando a destajo. No hemos parado ni un momento." Y así creemos que podemos estar tranquilos porque no se nos puede reprochar nada cuando lo hemos dado todo. Pero al final llega el día en que nos damos cuenta de que no hemos conseguido ninguna de las metas con las que soñábamos y nos preguntamos cómo fue posible, si estuvimos trabajando tanto.

Cuando veo a alguien que se pasa los días súper atareado haciendo mil cosas sin parar como si no hubiera un mañana tengo la tentación de preguntarle:
  • ¿Qué estás haciendo ahora mismo?
  • ¿Cómo lo estás haciendo? ¿Se podría hacer de otra forma que te llevara menos tiempo?
  • Y, sobre todo, ¿para qué lo estás haciendo? ¿Qué vas a conseguir con ello?
  • ¿Podrías estar haciendo otra cosa que realmente te aportara más de lo que estás haciendo ahora mismo?
Pero siempre me contengo y, al final, no digo nada. Al fin y al cabo, ellos están muy ocupados y probablemente no tendrán tiempo para escuchar a nadie.


Supongamos que estamos construyendo un sistema para la gestión de clientes donde, entre otras cosas, vamos a registrar las compras que realizan. Empezamos con un modelo muy básico, creando una clase para nuestros clientes que nos ofrecerá la funcionalidad necesaria para cumplir nuestra primera regla de negocio:

  • Son clientes senior aquellos que tienen más de año de antiguedad
Aquí tenemos algo de código:

Hemos empezado con un test que falla y continuamos desarrollando la implementación que hace que el test pase:

Seguimos con nuestro modelo. Como dijimos, los clientes compran productos, para los que tenemos un código y la fecha de compra (dicha fecha se establecerá en el momento en que la compra se haga efectiva, no pudiendo modificarse posteriormente). Así que introducimos una nueva clase para productos que ofrecerá una funcionalidad, bastante básica también, con otra regla de negocio:

  • Un producto ha sido comprado recientemente si la compra se produjo en el último mes
Y creamos un test para dicha funcionalidad:

A continuación, añadimos la lógica que hace que nuestro test pase:

Ahora que tenemos clientes y productos, vamos a añadir la lógica que permitirá a los clientes comprar productos. Empezamos, como siempre, por un test que falla:

Y, después de haber creado el test, implementamos dicha lógica y hacemos que el test pase:

Con el código que tenemos a estas alturas, podemos empezar a hacer cosas más interesantes. Nos proponemos hacer ofertas especiales a algunos clientes y de ahí sale nuestra tercera regla de negocio:

  • Sólo los clientes senior pueden recibir ofertas especiales.
Así que añadimos una nueva funcionalidad en nuestra clase de clientes, empezando con tests que fallan:

Y, a continuación, implementamos esta funcionalidad de acuerdo con nuestra regla de negocio para hacer que los tests pasen:

Nuestro código pasa a produción, la gente empieza a comprar y nosotros hacemos ofertas a nuestros clientes para aumentar las ventas. Al cabo del tiempo, decidimos cambiar las condiciones para hacer ofertas a clientes y modificamos nuestra tercera regla de negocio con una nueva versión:

  • Sólo los clientes senior que hayan realizado alguna compra recientemente pueden recibir ofertas especiales.
Como siempre, empezamos por los tests, así que creamos un nuevo test que nos asegure que esta nueva versión de nuestra regla de negocio se cumple. Pero, con el código de test que hemos creado hasta ahora, nos encontramos con la primera dificultad. La clase Product fue diseñada para ser inmutable, por lo que ni el código del producto comprado ni obviamente la fecha de compra deberían poder ser cambiados. Con nuestro código actual, necesitamos establecer una fecha de compra con más de un mes de antelación para poder construir un test que compruebe que los clientes que

  • tienen más de un año de antigüedad pero
  • no han comprado recientemente
no reciben ofertas especiales.

Aquí empiezan las dificultades pues tenemos que, para poder probar nuestro sistema, hay que violar el diseño que nosotros mismos habíamos definido. No pasa nada, hacemos un pequeño ajuste en la clase Product y, a partir de ahí, resulta mucho más fácil probarlo todo:

Ahora ya podemos implementar nuestra nueva regla en la clase Customer:

Y esto es lo que llaman Design for testability, ¿verdad? Hemos modificado nuestro diseño para poder probar el código correctamente. ¡Falso! Esto no es más que un diseño pobre que hemos enmendado de mala manera para poder crear unas mínimas pruebas unitarias, pero dista mucho de ser una buena solución y el problema no hará más que agravarse con el tiempo. El diseño para la prueba no tiene nada que ver con romper el principio de encapsulación y exponer el estado interno de un objeto permitiendo que actores externos puedan alterarlo sin ningún control.

Por supuesto, podríamos intentar usar algún tipo de objeto mock para los productos que compran los clientes, lo cual requeriría configurar dichos mocks para devolver las fechas de compra que necesitamos en nuestra prueba y modificar la propia clase Customer para poder inyectar dichos mocks como productos comprados por el cliente. Sin embargo, esto sería un engorro que nos va a hacer perder mucho tiempo cada vez que queramos probar algo y ya sabemos que hacer que las pruebas sean complicadas de programar supone abandonar la práctica de programar pruebas automáticas.

El problema no está en si la clase Product es inmutable o no, en cómo modificar la fecha de compra de los productos o en cómo injectar instancias mocks de productos en la clase Customer. El auténtico fallo de diseño en nuestro código está en que hemos dejado en manos de la clase Cutomer la creación de la fecha de compra de los productos. Es decir, cuando un cliente compra un producto, establecemos la fecha de compra del product (que debe ser el instance actual, es decir, ahora) y dejamos que sea la propia clase Customer la que decida cuándo es ahora.

El verdadero problema está en que la clase Customer es  responsable de determinar cuándo es ahora, es decir, de instanciar Date.
Un verdadero diseño que facilite las pruebas consiste en delegar la lógica que determina cuándo es ahora, es decir, crear una nueva instancia de la clase Date en el instante actual, a una clase externa que sirva como proveedor de tiempo. Con esta idea, la implementación no podría ser más sencilla:

Ahora ya no es Customer la clase responsable de crear objetos Date sino que los solicita a una clase externa, TimeProvider. De hecho, a partir de ahora ninguna clase en nuestro sistema debería jamás crear sus propias instancias de Date sino pedirlas siempre a TimeProvider.

Habréis notado algo raro en esta implementación de TimeProvider. ¿Qué es ese campo offset y para qué lo queremos? Offset no es ni más ni menos que nuestra implementación del concepto de design for testability. Veamos cómo funciona.

Supongamos, como caso más sencillo, que offset toma un valor de 0. A continuación, solicitamos a TimeProvider la fecha y hora actual llamando al método now(). Pero supongamos que offset tuviera como valor el incremento de tiempo correspondiente a tres días (en milisegundos) y le volvemos a pedir a TimeProvider la fecha y ahora actual, es decir, volvemos a llamar a now(). ¿Qué recibimos? La fecha y hora de dentro de tres días. ¿Y si todo nuestro sistema estuviera conectado a TimeProvider y cada vez que requiriese la hora actual llamara al método now()? Entonces nuestro sistema estaría viviendo en el futuro, tres días en el futuro, para ser exactos.

Para conseguir esto, vamos a construir una clase que solamente pueda ser utilizada por nuestro código de pruebas. Dicha clase nos permitirá ajustar el valor de offset dentro de TimeProvider de forma que siempre sume (o reste) una constante de tiempo al instante actual. Así es como acabamos de crear la máquina del tiempo:

Básicamente esta clase sirve para establecer el valor de offset en TimeProvider. Además le he añadido algunos métodos adicionales para permitir especificar el instante de tiempo al que queremos viajar como Date, LocalDateTime, etc. pero la idea básica es esa: cambiar el offset de TimeProvider. Ahora podemos reescribir nuestro código de pruebas para la nueva versión de nuestra regla de negocio de una forma más clara y sencilla:

Ya no necesitamos cambiar la fecha de compra de los productos ya que ésta se va a establecer en el instante correcto cuando llamamos al método buy(). Nuestra clase Product vuelve a ser inmutable tal y como queríamos. De hecho, todos los tests que hemos creado hasta ahora pueden ser reescritos usando TimeMachine, lo que nos va a ayudar a hacerlos más legibles:

Sólo hay un detalle final que tenemos que tener en cuenta. Como debemos mantener las ejecuciones de los distintos tests independientes unas de otras, es preciso que offset vuelva a cero cada vez que terminamos con una prueba (y también asegurarnos de que es cero cuando una prueba comienza). Para eso, TimeMachine nos proporciona un método reset() que se encarga de ello.

En resumen, hemos llegado a una solución con un mejor reparto de responsabilidades entre las distintas clases del sistema. Esta solución nos ha ayudado a mantener el diseño inmutable que queríamos para nuestra clase Product y ha conseguido que los tests sean más fáciles de escribir y de leer.

Incluso en el caso de que no estemos demasiado interesados en la inmutabilidad de objetos, seguir principios de Design Driven Domain o sencillamente no nos importe llenar nuestras clases de métodos de acceso getters y setters (bueno, hay gente para todo), el tener un proveedor centralizado de fecha y hora para todo el sistema nos puede resultar muy útil cuando estamos construyendo lógica fuertemente ligada a una cronología de eventos y acontecimientos (por ejemplo, cuando estamos implementando un flujo de trabajo y queremos probarlo de forma integral).

Aquí termina la presentación de mi máquina del tiempo particular, una ligera variación del arcano conocimiento que me fue transmitido por Dmytro Iaroslavskyi, un auténtico fenómeno. Espero que os haya resultado interesante esta solución. Por supuesto, cualquier comentario sobre cómo podéis aplicarla, qué modificaciones haríais, qué puntos débiles le veis o cualquier otra cosa que os venga a la mente será más que bienvenida.

Tenéis el código fuente utilizado como ejemplo para esta entrada en github.


Hay algo que está muy extendido entre los desarrolladores de software: la tendencia natural que tenemos a pensar siempre que existe un lugar de trabajo mejor que donde estamos. Sólo tenemos que encontrarlo para ser felices para siempre, al menos, laboralmente.

Lo cual, traducido a nuestras carreras profesionales, tiene su traducción en creer firmemente que la empresa donde trabajamos es una basura, que las condiciones son horribles, que el trabajo se hace mal (y que, en otra parte, existe una empresa donde las cosas se hacen realmente bien, donde nos sentiremos valorados, nos pagarán como nos merecemos y donde las condiciones serán tan buenas que ir a trabajar será mil veces mejor que irnos un mes de vacaciones a las Maldivas).

Normalmente este pensamiento no aparece instantáneamente nada más empezar a trabajar en una empresa. Algunas veces es un hecho concreto lo que lo dispara (una promoción que se nos ha escapado, un aumento de sueldo que no ha llegado, una discusión con nuestro jefe) aunque lo más habitual es que sencillamente se vaya formando en nuestra mente poco a poco, conforme la rutina empieza a impregnar cada aspecto de nuestro trabajo diario y la magia de estar en un sitio nuevo donde descubres algo todos los días se va perdiendo.

A partir de entonces empezamos a disfrutar cada vez menos de nuestro trabajo y, probablemente por ello, nos sentimos menos motivados. Lo que suele resultar, en casi todos los casos, en que nuestros resultados son peores. A veces, mucho peores (he visto a desarrolladores a los que consideraba genios hacer auténticas chapuzas y darme cuenta al final de que el problema estaba en que no se sentían motivados cuando las hicieron). Por supuesto, conforme los resultados son peores, la valoración que otros tienen de nosotros es peor, la promoción que esperábamos no llegará, tendremos cada vez más broncas con nuestro jefe, nuestro sueldo seguirá siendo igual de miserable. Y todo ello hará que cada vez nos sintamos más resentidos con nuestra empresa y más amargados en nuestro trabajo.

Pero no pasa nada, porque sabemos que existe ese otro sitio donde las cosas nos irían mucho mejor instantáneamente por la sencilla razón de que es un lugar mejor. Y entonces empezamos a buscar otro trabajo… de nuevo. Porque no es la primera vez que andamos buscando ese sitio. Es la misma búsqueda que hicimos la última vez que nos pusimos a buscar un nuevo trabajo (y que a lo mejor pensamos que había terminado cuando encontramos el trabajo donde estamos ahora). Si eres sólo un poco más listo que la mayoría de la gente ya te habrás dado cuenta de que tampoco va a ser la última vez que lo hagas.

Y así nos pasamos muchos de nosotros nuestra vida profesional, saltando de una empresa a otra regateando beneficios sin ser feliz en ninguna parte, hasta que llega el momento en el que empezamos a valorar más otras cosas como la estabilidad -por convencimiento o por pura necesidad. Y dejamos de buscar, no porque pensemos que hemos llegado al lugar correcto, sino sencillamente porque sabemos que ya no podemos seguir más adelante. Y terminamos en un lugar que no nos gusta, donde no disfrutamos ni un solo segundo que pasamos en él y de donde sabemos que ya no vamos a salir fácilmente.

Vale, estoy siendo un poco dramático pero quién puede decir que no reconoce un patrón parecido en su carrera o la carrera profesional de otros. Y, ¿realmente tiene sentido que sea así?

Rompiendo el círculo

Lo primero que tenemos que hacer para romper esta espiral de insatisfacción es darnos cuenta de una verdad: no existe la empresa perfecta. Sólo tienes que pensar en que la gente se va de Google porque está absolutamente quemada. Sí, los mismos que trabajan en esas oficinas tan chulas con salas de reuniones super guapas, restaurante propio donde te dan de comer chefs y te prepara el café que quieras un barista profesional, además de cobrar una pasta gansa, se terminan marchando porque están insatisfechos, aburridos, estresados, quemados.

¿Crees que serías más feliz si en vez de trabajar en tu empresa actual lo hicieras en Google? Pregúntales a los que se marcharon de Google (posiblemente te dirán que se fueron para trabajar en una empresa mejor). ¿Crees que vas a ser más feliz por trabajar en otra empresa? Puede que no porque las condiciones son sólo una parte pequeña de la ecuación de la satisfacción laboral. Cuanto antes te des cuenta de eso más tiempo tendrás para realmente planificar cómo quieres que evolucione tu carrera.

Conócete a ti mismo


Empieza por preguntarte qué es lo que realmente quieres conseguir de tu carrera profesional. ¿Te gustaría por encima de todo trabajar para una gran empresa? ¿Valoras más poder compartir tu lugar de trabajo con gente super inteligente? ¿Te gustaría dedicarte a gestionar en lugar de a hacer cosas por ti mismo? ¿Quieres trabajar en tu propia idea?

Mucha gente con la que he hablado no sabe en qué quiere que se convierta su carrera profesional. Muchos tienen dudas, les cuesta decidir, algunos ni siquiera se lo han preguntado. Y eso es lo que realmente nos hace infelices en el trabajo la mayoría de las veces.

Por eso, da igual lo que quiera que escojas, debes escoger algo y tener presente que no se puede tener todo. Si nos hicieran por separado cualquiera de las siguientes preguntas:

  • ¿Te gustaría que tus compañeros de trabajo fueran personas super inteligentes de las que pudieras aprender todos los días?
  • ¿Te gustaría trabajar en algo con un impacto global que potencialmente afecte a millones de usuarios?
  • ¿Te gusta ver que tu trabajo diario tiene un impacto directo en los resultados de tu compañía?
  • ¿Quieres ascender la escalera corporativa y llegar a ocupar el cargo con mayor responsabilidad posible?
  • ¿Quieres desarrollar tu propia idea (revolucionaria) y cambiar el mundo?

Posiblemente responderíamos que sí a un buen número de ellas. Pero, puestas todas juntas, es imposible decir que sí a todas a la vez (al menos, de una forma realista). Sencillamente son cosas diferentes, en muchos casos contradictorias, que requieren seguir caminos divergentes para alcanzarlas. Así que tenemos que decir que sí a unas pocas cosas y empezar a decir que no a muchas otras.

Y empieza a moverte


Y cuando ya sepas adónde quieres ir entonces es el momento de empezar a moverte, de buscar siempre mejores oportunidades para seguir desarrollándote de la forma que has decidido hacerlo. Puede ser que un trabajo te dure unos pocos meses hasta que cambies a otro o bien que continues en la misma empresa durante años. En cualquier caso, cada paso que des lo harás convencido de que te acerca a tu objetivo. Y si tu objetivo cambia, no pasa nada, ajusta tu plan y empieza a ponerlo en marcha.

Los planes son inútiles, pero planificar lo es todo.
Dwight D. Eisenhower

No ocurre nada si cuando llegas a una entrevista de trabajo presentas un curriculum que demuestra que durante 5 años quisiste ser experto en inteligencia artificial y durante los dos años siguientes cambiaste porque querías ser scrum master o agile coach. No se puede decir lo mismo si cambiaste diez veces de empresa para conseguir dos días más de vacaciones o una silla más cómoda.

Para quien navega sin rumbo ningún viento es favorable.
Séneca


Siempre recomiendo utilizar cuantos más componentes ya disponibles mejor. Nunca construyas tu propio framework para:

  • Persistir datos
  • Mostrar información basándote en plantillas
  • Construir la navegación de un sitio web
  • Comunicar componentes remotos
Y tantos otros ejemplos que me vienen a la cabeza.

Sin embargo, hay un framework que siempre deberías construir en cualquier proyecto en el que vayas a participar. Es algo que deberías tener claro antes incluso de empezar. Cuanto antes lo construyas será mucho mejor y no hacerlo pone seriamente en riesgo tu proyecto.

Se trata de tu propio framework para automatizar tus pruebas. No es algo que puedas tomar de alguna otra parte, ni que puedas copiar y pegar de un proyecto pasado (aunque, evidentemente, haber construido uno en el pasado te ayudará a hacerlo ahora). Y no puedes por una sencilla razón: todavía no ha sido inventado. Y el único que puede inventarlo eres tú porque sólo tendrá sentido para ti.

Pero espera un momento entonces. Si sólo va a tener sentido para mí, ¿sigue siendo un framework? Sí y no, lo veremos más adelante. Ahora mejor empecemos con un ejemplo.

Registrando usuarios en una aplicación web

Imaginemos que hemos construido nuestra nueva aplicación web y, como sabemos que vamos a tener que iterar muchas veces sobre nuestro producto para dejarlo a punto, tenemos claro que necesitamos tantas pruebas automáticas como sea posible. De lo contrario, cada vez que cambiemos algo estaremos corriendo un serio riesgo de que el sistema falle (básicamente nos hemos leído algún libro del tipo The Lean Startup, algo sobre desarrollo ágil o, por lo menos, algún artículo en internet publicado en los últimos 10 años. Así que decidimos empezar creando una prueba para una parte fundamental de nuestra aplicación: el login de usuarios.

Como estamos empezando y todavía no sabemos muy bien cómo será el producto final, decidimos hacer una versión muy sencilla (ver ejemplo).

http://cdpn.io/yqihp

Lo sé, es tan fea que casi hace que duelan los ojos. Pero tampoco Google tenía muy buena pinta cuando empezó y fíjate dónde está ahora. Así que no te preocupes, posiblemente cuando vayas por la iteración número 15 podrás pensar en dejarla bonita. De momento, nos sirve. El código HTML de nuestra página sería:

Automatizando pruebas

Podemos crear una prueba automática que haga login en nuestra aplicación usando, por ejemplo, Selenium:

A partir de aquí, podríamos usar este mismo código en cualquier test que requiera el login del usuario y todo estaría bien... hasta que llegásemos al punto en el que queramos cambiar nuestra pantalla de login (iba a ser en nuestra iteración número 15, ¿no?) y tengamos que modificar cientos o, si hemos sido realmente buenos, miles de tests que se basaban en el código anterior.

Mejorando nuestras pruebas automáticas

El problema anterior básicamente tiene su origen en que hemos hecho nuestro código de prueba dependiente de algo que cambia de forma frecuente (especialmente en las etapas iniciales de un proyecto): la interfaz de usuario. Así que algo tan trivial como cambiar el identificador a los elementos HTML de la página de login puede hacer que cientos de tests empiecen a fallar.

La solución, como otras tantas veces, consiste en añadir un nivel de indirección que se encargue de manejar los detalles acerca de cómo está implementada la interfaz de usuario y, al mismo tiempo, ofrezca una interfaz consistente y homogénea con la que puedan interactuar todos los tests que construyamos. Así es como empezaremos a construir el framework que nos ayudará a automatizar nuestras pruebas.

En nuestro caso, aquellos tests que necesiten hacer un login de usuario se podrían escribir de la siguiente manera:

Una vez empecemos con la construcción de nuestro propio framework podemos ir más allá de la simple abstracción de la UI. Por ejemplo, podemos hacer cosas como:

  • Ofrecer un acceso rápido a usuarios ya creados para los que además conocemos determinados parámetros interesantes desde el punto de vista de las pruebas.
  • Implementar procesos largos que son repetidos por muchos tests.
En mi actual proyecto, por ejemplo, tenemos una serie de usuarios prototípicos que son utilizados repetidamente por toda nuestra suite de pruebas. Por ejemplo, Thomas Müller: como buen alemán medio que es, tiene contratado un seguro de responsabilidad civil (también tiene el nombre y el apellido más repetido en Alemania). Así que siempre que alguien necesita un usuario que tenga registrado un seguro de responsabilidad civil durante la construcción de una prueba sólo tiene que hacer:

TestUsersRegistry.getThomasMueller();

O si queremos crearlo desde cero:

TestUsersFactory.createThomasMueller();

O cuando es necesario registrar un usuario nuevo, sin contratos, para probar una funcionalidad disponible para usuarios recieén registrados (tenemos unas cuantas de ese tipo) sólo hace falta hacer:

TestUsersUtils.registerNewUser("John", "Doe", "john.doe@example.com");

En este caso, este método está sobrecargado con diferentes opciones para darnos la flexibilidad necesaria que nos permita registrar usuarios con todas las opciones que sean relevantes desde el punto de vista de los casos de prueba que estamos automatizando. Lo que nos lleva al siguiente punto importante cuando creas tu propio framework de automatización de pruebas: complejidad.

Características de un framework de automatización de pruebas

Por su propia naturaleza, el códido que escribimos cuando automatizamos pruebas tiene (o sería deseable que tuviera) las siguientes características:

  • Es bastante repetitivo. Muchas acciones se repiten a lo largo de múltiples tests (especialmente cuando implementamos diferentes escenarios para una misma funcionalidad)
  • Es lineal (o debería serlo; si no lo es entonces deberías revisar cómo estás implementando tus pruebas)
  • Es fácil de escribir (porque queremos que escribir tests sea algo sencillo que todo el mundo haga de forma regular)
  • Es fácil de leer. Esto es más importante aún porque:
    • Queremos podemos entender más fácilmente qué esperamos de una funcionalidad con echarle un ojo al código que la prueba
    • Cuando un test falla y queremos arreglarlo lo último que queremos es tener que perder un montón de tiempo averiguando qué es lo que el test estaba intentando hacer
  • Se aproxima lo máximo posible al lenguaje natural y utiliza cuantos más términos propios del dominio de nuestro sistema mejor. Esto es básicamente un corolario del punto anterior, pero quería resaltarlo para dejarlo bien claro.
Por todo esto, queda claro dónde tenemos que poner la complejidad: en nuestro framework de automatización de pruebas. Porque las cosas nunca son sencillas y, si queremos tener un código de prueba que sea super sencillo y fácil de entender, tiene que haber un sitio donde se encuentren todas las complejidades inherentes al código que estamos intentando crear.

Por eso, no es ningún problema que nuetro código de automatización de pruebas sea complicado. Tiene que serlo para que nuestras pruebas sean sencillas. La parte positiva es que será un código que escribirás una vez y lo reutilizarás miles de veces (el beneficio es claro).

Recuerda: no se trata de hacer las cosas más complicadas de lo necesario por el gusto de hacerlas complicadas. Pero no hay ningún problema si tu framework de automatización de pruebas empieza a serlo porque tu objetivo es hacer sencillas las miles de pruebas que quieres automatizar.

Esto nos lleva a las características deseables para nuestro framework de automatización de pruebas:

  • Permite escribir muchos tests con poco esfuerzo.
  • Ofrece interfaces claras que pueden ser utilzadas fácilmente por quienes tengan que escribir las pruebas automáticas.
  • Se acerca lo máximo posible al lengaje natural (lo que permite que las pruebas automáticas puedan ser escritas por más personas distintas a los propios desarrolladores del sistema).
  • Maneja todas las complejidades que puedan aparecer cuando tenemos que escribir el código de una prueba.
  • Es lo suficientemente flexible para facilitar la creación de pruebas para todos los escenarios que necesitemos (no sólo para los más sencillos).
  • Incorpora atajos que hagan la vida más cómoda a quienes escriben los tests. Por ejemplo, si hemos parametrizado el método que crea usuarios para que se puedan crear todos los tipos de usuarios con los que nos interesa probar, es perfectamente válida crear métodos adicionales que permitan crear determinados tipos de usuarios de forma fácil (sin tener que pasar ningún parámetro, por ejemplo) aunque eso pudiera considerarse redundante.
Al principio, crear tu framework de automatización de pruebas puede parecer una tarea complicada pero te aseguro que el beneficio es demasiado grande para ignorarla. Además, es parecido a ir al gimnasio: puede costar mucho al principio pero, cuando te acostumbras, no cuesta tanto y, finalmente, verás que lo haces con naturalidad.

Es también una excelente inversión porque, una vez hecho el esfuerzo inicial y puesto en marcha el framework, verás cómo cada vez inviertes menos tiempo en desarrollarlo y perfeccionarlo y, en cambio, pasas más tiempo usándolo y creando más y más pruebas automáticas lo que te ayuda a mejorar la calidad del sistema que estás construyendo (y, de paso, te hacer sentire mucho mejor como desarrollador y ser humano).

Comentarios finales

Aquellos que están familiarizados con temas como automatización de pruebas, TDD,  etc. puede que se hayan sentido un poco confundidos por haber dejado en el aire algunas cuestiones importantes. Como, ¿para qué tipos de pruebas es importante crear nuestro framework? ¿Sólo para pruebas de aceptación? ¿Sólo cuando estamos probando a través de la UI, como en el ejemplo que hemos visto con Selenium?

Lo cierto es que he sido deliberadamente impreciso en este aspecto. En cualquier caso, debemos crear nuestro framework para cualquier tipo de prueba que automaticemos en nuestro proyecto, no importa que sean unitarias o integradas, de aceptación, de sistema... incluso para pruebas de humo.

Siempre que estemos repitiendo el mismo código una y otra vez cada vez que creamos un test y/o que dicho código dependa de cosas que van a cambiar con frecuencia estamos recibiendo una señal de que necesitamos refactorizar nuestro código de pruebas y empezar a crear nuestro propio framework. Es algo que debemos hacer a múltiples niveles y que tenemos que hacer con naturalidad (casi como respirar).

Referencias

Mi libro favorito sobre este tema, y con el que aprendí a escribir pruebas automáticas de una forma sostenible en el tiempo (y no el lío de código espagueti en el que solía convertir mis tests), es xUnit Test Patterns de G. Meszaros. Casi diría que es una lectura obligatoria (de la que ya hablé anteriormente aquí). Si no lo has leído, puedes empezar echándole un ojo al sitio web del libro, donde hay información muy útil.


Hace un tiempo un amigo me pidió que le recomendara algunas lecturas apropiadas ahora que se iba a dedicar oficialmente a ejercer como arquitecto de software (¿todavía alguien quiere ser arquitecto de software?). Al final, nunca le envié la lista pese a que me pareció una pregunta de lo más interesante.
Ahí van mis diez lecturas recomendadas para cualquier ingeniero de software, en ningún orden en particular:


No es una lista exhaustiva ni son todos los libros que te recomendaría. Podría haber incluido en la lista xUnit Test Patterns: Refactoring Test Code o The Pragmatic Programmer: From Journeyman to Master. O tantos y tantos grandes libros que hay. Pero, sinceramente, creo que si llegas a leerte todos estos libros y te dedicas de forma habitual al desarrollo de software siguiendo lo que en ellos se dice, habrás alcanzado el punto en que te convendría mucho más leer cosas no relacionadas tan directamente con el desarrollo del software. Anque ése es otro tema del que hablaremos en el futuro.

Notas finales

Para que no sirva de excusa el tema del idioma, os dejo los siguientes con sus enlaces a la edición en español: