¿Cuál debe ser mi primer test? ¿Qué debo comprobar a continuación? Son preguntas habituales que me hacen una y otra vez. Yo mismo me las hago a menudo. Decidir qué testear es difícil. Decidir el orden en que se deben probar las cosas es aún más difícil.1 Pero escribir primero los tests no es el único problema. ¿Cuántas veces nos hemos frustrado al tratar con tests existentes que no teníamos ni idea de qué estaban probando?
A lo largo de los años he conocido y hecho pairing con un montón de practicantes experimentados de TDD, y cada uno de ellos tiene una forma ligeramente diferente de decidir qué pruebas escribir y en qué orden. También tienen distintos enfoques para escribir sus tests. Algunos escriben primero el nombre del método de prueba, otros lo denominan apropiadamente después de escribir el código de prueba. Hay quienes prefieren un enfoque más exploratorio y sólo refactorizan cuando tienen una mejor comprensión del problema y suficiente código delante de ellos. Otros prefieren un enfoque más estructurado, pensando un poco más y haciendo un diseño just-in-time antes de escribir las pruebas. Algunos empiezan a escribir directamente y esperan a ver qué aspecto tendrá el código antes de hacer suposiciones sobre nombres y diseño. Otros adoptan por defecto un enfoque más clasicista. También hay quienes prefieren un enfoque mockist outside-in. Y, para hacer las cosas más confusas, los desarrolladores expertos en TDD mezclan y combinan estilos y enfoques según lo que estén intentando probar.
En este blog intentaré describir cómo pienso generalmente en mis tests. Debido a la naturaleza de los proyectos en los que normalmente estoy involucrado2 tiendo a pensar y diseñar (en mi cabeza) antes de empezar a escribir. Outside-In TDD es normalmente mi modo por defecto cuando escribo tests. Mi principal objetivo al escribir pruebas es expresar claramente el comportamiento que quiero que tenga la aplicación (o clase).
Esta es la plantilla que utilizo normalmente para decidir qué testear y cómo nombrar mis métodos de prueba de acuerdo con el comportamiento esperado:
Este enfoque me obliga a pensar en el comportamiento que quiero que tenga mi clase, lo que facilita la escritura de mis primeros tests y los siguientes. Incluso a nivel de tests unitarios, hago todo lo posible por nombrar mis métodos de prueba de forma que una persona de negocios pueda entenderlos, rara vez utilizando lenguaje técnico. Por ejemplo:
Intentar formar una frase combinando el nombre de la clase de prueba y el nombre del método de prueba me obliga a centrarme realmente en el comportamiento que quiero testear. Una vez que lo averiguo, resulta bastante fácil escribir mi aserción, ya que solo tengo que traducir el inglés al lenguaje de programación que estoy utilizando.
Un aspecto a tener en cuenta es que normalmente divido el cuerpo de mis tests en 3 bloques (given, when, then). Sin embargo, en algunos tests llamo al método bajo prueba desde la aserción y algunos tests no necesitan ninguna configuración. Muchas pruebas son bastante simples y sólo tienen una línea.
Estos son los 5 pasos que sigo normalmente cuando creo una nueva clase de prueba:
Para los siguientes métodos de prueba, itero a través de los pasos 2 a 5. El esqueleto del código de producción se genera a partir de la clase de prueba.
¿Por qué utilizo "debería"?
A muchos desarrolladores no les gusta el uso de la palabra "debería". Algunos dicen que es redundante y que no conviene utilizarla. Otros dicen que no es lo suficientemente fuerte. "Debe" es demasiado fuerte. ¿Y qué tal "tener que"? Sigo sin estar seguro. Por ahora me quedo con "debería", pero puede que me decida por "tengo que". Como sólo lo utilizo en el nombre de la clase, no creo que sea gran cosa y me ayuda a construir una frase al combinarlo con el nombre de los métodos de prueba.
Malas denominaciones
Estos son algunos nombres comunes que he visto dar a la variable que hace referencia a la clase bajo prueba:
BankAccount testee = new BankAccount();
BankAccount sut = new BankAccount();
BankAccount ba = new BankAccount();
Si se trata de una cuenta bancaria, nombra la variable bankAccount.
Y para los métodos:
test_deposit_works() {…}
test_deposit_works_correctly() {…}
test_deposit() {…}
check_balance_after_deposit() {…}
Un nombre de prueba debe indicar claramente por qué una prueba debe pasar o fallar, es decir, no debería necesitar mirar la(s) aserción(es) para averiguar qué está probando realmente el test.
[BankAccountShould] have_balance_increased_after_a_deposit()
Si la prueba anterior falla, tendré una idea bastante clara de por qué ha fallado.
Consideraciones finales
En realidad no sigo las normas y a menudo hago las cosas de otra manera. Todo depende del contexto en el que me encuentre. Algunos proyectos ya cuentan con una forma estándar de escribir tests unitarios y, si soy un recién llegado, me atengo a ella. Tener un estándar es bueno, evita confusiones y (malas) sorpresas. Lo que he descrito es cómo suelo nombrar y pensar mis pruebas, y es la pauta que aplico cuando empiezo un nuevo proyecto. También es la forma en que enseño a nuestros aprendices en Codurance.