DEV Community

Joel Humberto Gómez Paredes
Joel Humberto Gómez Paredes

Posted on

Escribiendo tests mas claros, simples y de amplio espectro con Table Driven Testing con jest

El testing es una parte importante dentro del ciclo de vida del desarrollo de software, con el tiempo se han desarrollado metodologías, patrones y herramientas alrededor de esta. Table Driven Test es una “técnica” que se enfoca en hacer uso de una tabla para representar nuestras entradas y la salida esperada.

Esta técnica es bastante útil cuando queremos probar muchos escenarios (combinaciones de posibles entradas y salidas). Vamos a usar como ejemplo la ya conocida función que suma, se reciben como parámetros 2 números (a, b) y se regresa el resultado.

function sum(a, b) {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Es un hechizo simple pero inquebrantable

Antes de pasar a escribir el test vamos a hacer una representación de como serían nuestras entradas y salidas usando Table Driven Test

¿Algo bastante simple no?

Bueno ahora se viene lo interesante, vamos a pasarlo a código usando Table Driven Test usando jest (porque es popular y es lo que uso actualmente).

Para esto tenemos 2 opciones, usar template literals o arreglos bi-dimensionales.

Template literals

test.each`
  a | b | output
  ${2} | ${2} | ${4}
  ${100} | ${200} | ${300}
`('SHOULD sum a($a) and b($b) and return $output', ({ a, b, output}) => {
  expect(sum(a, b)).toBe(output);
});
Enter fullscreen mode Exit fullscreen mode

Al ejecutar las pruebas con jest obtenemos los siguientes resultados,

Tests passing

A diferencia de un test tradicional tenemos acceso a los valores con los que se va a ejecutar el test usando ${nombre de la variable en la table} en la descripción y en la función que ejecutará los test simplemente des-estructuramos el objeto.

Esto hace a nuestro test mas claro, porque la descripción incluye con que se esta probando, simples y de amplio espectro porque si deseamos podemos agregar más escenarios podemos agregar otra row sin tener que tocar código de la prueba, no modificamos cómo se hace el test ni nada similar.

test.each`
  a | b | output
    ${0} | ${0} | ${0}
  ${2} | ${2} | ${4}
  ${100} | ${200} | ${300}
`('SHOULD sum a($a) and b($b) and return $output', ({ a, b, output}) => {
  expect(sum(a, b)).toBe(output);
});
Enter fullscreen mode Exit fullscreen mode

Este resultado también sería posible si dentro del test case usamos arreglos y tratamos de ser “inteligentes” al escribir nuestros tests (no lo recomiendo)

test('SHOULD return sum a and b', () => {
  const testTable = [
    [2, 2, 4],
    [100, 200, 300]
  ];
  testTable.forEach(([a, b, output]) => {
    expect(sum(a, b)).toBe(output);
  });
});
Enter fullscreen mode Exit fullscreen mode

Al ejecutar nuestras pruebas podemos observar esto en consola

Tests passing

La principales diferencias son que la descripción del test cambió porque al no tener acceso a los valores que vamos a probar tenemos que repensar cómo describir el test y que al parecer solo se ejecuta un test case lo cual sabemos no es cierto.

Por esto en lo personal no estoy de acuerdo con ser “inteligente” al escribir los tests, porque corremos el riesgo de agregar lógica y agregar errores. Los tests deben ser descriptivos aunque a veces sean repetitivos (debe haber un balance)

Una buena práctica y un buen indicador de que tienen un código robusto es poder entender cómo funciona el software leyendo sus tests en vez de leer el código directamente.

Anotenle

Arreglos bi-dimensionales

Ahora vamos a hacer el mismo test pero usando arreglos bi-dimensionales

const testTable = [
  [2, 2, 4],
  [100, 200, 300]
];
test.each(testTable)('SHOULD sum a(%d) and b(%d) and return %d', (a, b, output) => {
  expect(sum(a, b)).toBe(output);
});
Enter fullscreen mode Exit fullscreen mode

La diferencia más notoria es el uso de format specifiers para tener acceso a los valores de la tabla de pruebas en la descripción del test y que los parámetros en nuestra función ahora son tomados uno por uno en lugar de hacer uso de un objeto.

¿Cuándo usar una u otra? Depende de ti, es una decisión personal aunque yo sugeriría que arriba de 10 casos de uso es mejor usar arreglos. La desventaja es que el tests perdería un poco de claridad pero ganaría legibilidad.

Conclusión

Table Driven Test es una mejor manera de escribir tests en código que tiene múltiples escenarios y en los cuales queremos tener bastante claridad de que estamos probando y con qué lo hacemos. No todo debe ser Table Driven Test porque no todo se puede probar de la misma manera pero es sumamente útil.

Si quieres ver este proyecto puedes verlo en el siguiente monorepo.

Top comments (0)