Leyendo el libro Test-Driven Java
Development de publicado por Packt Publishing en
2015, he encontrado un ejemplo a modo de Kata (demostración sencilla de
la técnica) que ilustra muy bien cuáles son las ventajas de esta forma de
construir código.
Para comprender este post, es
necesario tener conocimientos básicos de: qué es TDD, un lenguaje de
programación orientado a objetos (aquí se usa java) y entender que es un marco
de pruebas unitarias XUnit (aquí se usa JUnit).
Una de las piezas clave para
hacer un buen desarrollo TDD es tener la capacidad de desgranar la
funcionalidad a implementar en muy pequeñas partes. Esta capacidad de idear
partes mínimas a programar en cada ciclo Green-Red-Refactor es imprescindible
si queremos ciclos cortos, a ser posible de minutos, tal y como recomienda la
técnica. En este texto los autores presentan el ejemplo que procedo a explicar
y que según mi criterio muestra la técnica a la perfección.
Ejemplo: Tres en raya
Tres en raya es un juego en el
que sobre un tablero de 3x3 casillas, 2 jugadores juegan por turnos rellenando
una casilla cada vez. Gana el jugador que consigue tener 3 casillas rellenas
alineadas en horizontal, en vertical o en diagonal.
El objetivo del ejemplo es
implementar el código necesario para jugar a 3 en raya. Este es el requisito
que el programador recibe. El desarrollo debe comenzar por algún trozo de código
que se pueda probar y desarrollar preferiblemente en minutos. En este ejemplo
se propone empezar por:
1. Primer ciclo red-green-refactor
Definir los límites que en
horizontal no puede sobrepasar una pieza. Es decir la pieza solo puede estar en
las casillas 1, 2 o 3.
Para empezar se hace un caso de
prueba para comprobar que la pieza no se intenta colocar fuera del rango,
provocando una excepción en este caso. Para detectar la excepción en la prueba con
JUnit recurrimos a la anotación @Rule.
@Rule
public ExpectedException exception =
ExpectedException.none();
private TicTacToe ticTacToe;
@Test
public void whenXOutsideBoardThenRuntimeException() {
exception.expect(RuntimeException.class);
ticTacToe = new Tictactoe();
ticTacToe.play(5, 2);
}
Esta es la fase red del ciclo
TDD, puesto que la ejecución de la prueba no es satisfactoria ya que ni siquiera
existe la clase Tic-tac-toe. Ahora se crea esta clase.
public
class TicTacToe {
public void play (int x, int y) {
if (x < 1 || x > 3) {
throw
new RuntimeException("X is
outside board");
}
}
}
Ahora se pasa a la fase green, la
ejecución de la prueba ya es satisfactoria.
En la fase refactor, modificamos
la prueba introduciendo la anotación @Before para crear el objeto tic-tac-toe.
Esto se hace porque van a existir más pruebas y estas también necesitarán crear
un objeto de esta clase.
@Rule
public ExpectedException exception =
ExpectedException.none();
private TicTacToe ticTacToe;
@Before
public final void before() {
ticTacToe = new TicTacToe();
}
@Test
public void whenXOutsideBoardThenRuntimeException() {
exception.expect(RuntimeException.class);
ticTacToe.play(5,
2);
}
De esta forma se ha completado un
ciclo red-green-refactor. ¿Cuánto tiempo puede llevar este trabajo de
codificación y pruebas? Seguramente no más allá de minutos.
2. Segundo ciclo red-green-refactor
El siguiente ciclo red-green-refactor
se dedica a la funcionalidad para las casillas verticales. Se siguen los mismos
pasos, lo primero el caso de prueba.
@Test
public void whenYOutsideBoardThenRuntimeException() {
exception.expect(RuntimeException.class);
ticTacToe.play(2,
5);
}
Estamos en fase red no hay
tratamiento para el valor de Y. Importante la prueba de X sigue funcionando. Se
añade el tratamiento para Y.
public class TicTacToe {
public void play (int x, int y) {
if (x < 1 || x > 3) {
throw
new RuntimeException("X is
outside board");
} else if (y < 1 || y > 3) {
throw
new RuntimeException("Y is
outside board");
}
}
Estamos en fase green. En este momento
se repasa el código y se decide no refactorizar nada. Es decir no hay nada que
mejorar.
3. Tercer Ciclo red-green-refactor
La casilla en la que se coloca la
pieza no puede estar ocupada. Primero la prueba.
@Test
public
void whenOccupiedThenRuntimeException() {
ticTacToe.play(2, 1);
exception.expect(RuntimeException.class);
ticTacToe.play(2, 1);
}
Después de fase red la
implementación.
public class TicTacToe {
private Character[][] board = {{'\0', '\0', '\0'},
{'\0', '\0', '\0'}, {'\0', '\0',
'\0'}};
public void play (int x, int y) {
if (x < 1 || x > 3) {
throw
new RuntimeException("X is
outside board");
}
else if (y < 1 || y > 3) {
throw
new
RuntimeException("X is outside board");
}
if (board[x - 1][y - 1] != '\0') {
throw
new RuntimeException("Box is
occupied");
} else {
board[x - 1][y - 1] = 'X';
}
}
}
Después de fase green la
refactorización. En refactorización se decide reorganizar la clase para mejorar
la legibilidad del código.
public class TicTacToe {
public void play(int x, int y) {
checkAxis(x);
checkAxis(y);
setBox(x, y);
}
private
void checkAxis(int axis) {
if (axis < 1 || axis > 3) {
throw
new
RuntimeException("X is outside board");
}
}
private
void setBox(int x, int y) {
if (board[x - 1][y - 1] != '\0') {
throw
new
RuntimeException("Box is occupied");
} else {
board[x - 1][y - 1] = 'X';
}
}
}
Se comprueba que la prueba sigue
terminando con éxito después de la refactorización. Todo preparado para
continuar con el siguiente ciclo hasta que se termine de implementar el juego
tres en raya al completo.
Conclusión
Este ejemplo muestra cómo mediante TDD vamos
construyendo un código que además de tener pruebas unitarias automatizadas,
está refactorizado no introduciendo deuda técnica. Esta forma de desarrollo también
dota al código de un diseño que poco acoplado y muy cohesivo.
Si el ejemplo te está resultando interesante puedes ver
la continuación en el post “ Entender
Test Driven Development (TDD) con un ejemplo (Parte 2 de 2)” en este mismo blog.
Soto de Real, 8 de agosto de 2019.
Madrid.
España.
No hay comentarios:
Publicar un comentario