Cómo NO escribir tests unitarios: Replicando cálculos en el test

Los tests unitarios son una de las herramientas más útiles para escribir código de calidad. Aunque a primera vista parece algo sencillo, escribir buenos tests unitarios no es una tarea trivial y hay que tener en cuenta qué queremos testear y cómo lo queremos testear.

No me considero un experto en el tema, pero llevo usando tests unitarios (a veces aplicando TDD y a veces no) unos cuantos años y después de muchas cagadas tanta experiencia acumulada tengo claras unas cuantas cosas que no se deben hacer.

Algunas de las cosas que voy a contar son bastante obvias, pero a veces lo más obvio es lo primero que pasamos por alto.

No repitas el algoritmo testeado en el test

Uno de los objetivos primordiales de los test unitarios es comprobar que el código que estamos escribiendo funciona correctamente.

Sin embargo hay veces que por la forma en que escribimos el test, lo único que estamos comprobando es que el código que estamos testeando usa el mismo algoritmo que se usa en el test:

public class Person
{
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public string FullName { return FirstName + " " + LastName; }
}

[Test]
public void FullNameIsFirstNameAndLastName()
{
	var p = new Person { FirstName = "Peter", LastName = "Parker" };
	Assert.That(p.FullName, Is.EqualTo(p.FirstName + " " + p.LastName));
}

Al repetir el algoritmo para calcular el resultado en el test si existe un bug en el código de producción, también existirá en el código de test y no lo detectaremos.

En general, es mejor hacer que los tests comparen siempre con valores constantes, en lugar de calcular el resultado en el propio test y compararlo con la salida del método o el estado del objeto que estamos testando:

[Test]
public void FullNameIsFirstNameAndLastName()
{
	var p = new Person { FirstName = "Peter", LastName = "Parker" };
	Assert.That(p.FullName, Is.EqualTo("Peter Parker"));
}

En este test no estamos diciendo nada sobre cómo se debe construir el nombre completo, sólo estamos indicando cuál debe ser el resultado, y eso nos lleva a una de las normas fundamentales a la hora de escribir un test: testear el qué, no el cómo.

Cuándo saltarse esta norma

Como siempre, nada es aplicable a todos los casos y hay veces en que las que compensa saltarse la regla anterior.

Supongamos que tenemos implementado un algoritmo que estamos seguros de que funciona, ya sea porque lo hemos testeado, porque es muy claro, o por el motivo que esa. Si ese algoritmo no nos sirve, por ejemplo porque es muy lento, o consume mucha memoria, podemos aprovechar el algoritmo en el que confiamos para testar nuevos algoritmos o nuevas implementaciones del mismo algoritmo y así realizar multitud de pruebas automatizadas que nos den seguridad de que el nuevo algoritmo funciona.

Por ejemplo:

for (var i = 0; i < 1000; i++)
{
	var data = GenerateRandomData();

	var expected = Array.Sort(data);
	var actual = NewSuperSortAlgoritm(data);

	ArrayAssert.AreEquals(expected, actual);
}

Este código podría comprobar un nuevo algoritmo de ordenación es correcto generando gran cantidad de casos de test y comparando que el resultado del nuevo algoritmo de ordenación coincide con el de una implementación que sabemos que funciona. Conseguir realizar ese número de pruebas usando casos de test manuales es imposible.

Conclusión

Aunque haya casos en los que esté justificado, en general es mala idea que un test use para comparar un valor que se haya calculado en el propio test. Si además ese valor lo hemos calculado en el test usando el mismo algoritmo que en el código de producción, el valor del test disminuye aún más.

Es mejor utilizar valores constantes para comprobar el resultado del test, ya que eso independiza el resultado del algoritmo empleado para calcularlo, haciendo que los tests estén menos acoplados (conceptualmente) del código que están testeando.

2 comentarios en “Cómo NO escribir tests unitarios: Replicando cálculos en el test

  1. hola, cuál es la manera más recomendable de hacer pruebas unitarias sobre metodos o funcionalidades que almacenan o modifican bases de datos? usar mocks? trabajar haciendo que los cambios se reflejen sobre la base de datos?

    muy buenos tus post

  2. Hola Carlos,

    Yo lo que suelo hacer es intentar aislar al máximo la lógica de la aplicación de la interección con la base de datos. Así puedo utilizar tests unitarios normales y corrientes sin ningún tipo de dependencia. Tienes un ejemplo en este post: https://blog.koalite.com/2013/07/refactorizando-tests-unitarios-hacia-tests-sin-dependencias/

    Si lo que quieres es testear el comportamiento de la base de datos, por ejempo validar que una consulta devuelve la información correcta o que se aplican las restricciones unique adecuadas al insertar datos, puedes aplicar la técnica descrita en estos posts: https://blog.koalite.com/2014/05/conceptos-basicos-para-testear-una-base-de-datos/

    Un saludo.

Comentarios cerrados.