Contenido

Unit tests en Java (II)

En el último artículo comencé a explicar algunas cosas sobre los unit tests en Java. Este artículo comienza donde lo dejamos en el anterior.

En este caso veremos cómo emplear las TestSuites para poder evolucionar el modelo anterior.

Java

El problema

Como siempre, partimos de un problema y vamos caminando hacia la solución. En este caso vamos a tratar de añadir una nueva funcionalidad a nuestra clase de estadística: vamos a calcular la media de un vector de números.

Como ya dijimos, trataremos de crear el test primero, siguiendo la práctica de TDD. Veremos también cómo es la necesidad la que nos obliga a tomar ciertas decisiones, y no el azar.

Primer problema: Proveedores de datos.

Como hemos dicho, vamos a hacer el test. Esto supone un problema, ya que el test anterior usaba proveedores de datos y eso me ha obligado a cambiar el constructor. Aunque no fuera así, por desgracia se llamaría a la función con los parámetros del proveedor. Ninguna de estas situaciones es buena. Por eso vamos a necesitar utilizar una TestSuite.

Lo primero que haremos será renombrar nuestra clase StatsTest, ya que no está probando la clase, sino sólo uno de los métodos. Así que es más correcto llamarla StatsMedianaTest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// file src/test/java/org/magmax/stats/StatsMedianaTest.java
package org.magmax.stats;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;

@RunWith(Parameterized.class)
public class StatsMedianaTest {

	private Stats sut;
	private int expected;
	private int[] input;

	@Before
	public void setUp() {
		sut = new Stats();
	}

	@Parameterized.Parameters
	public static Collection numbers() {
		return Arrays.asList(new Object[][]{
					{0, new int[]{}},
					{5, new int[]{5}},
					{3, new int[]{1, 3, 5}},
					{4, new int[]{2, 4, 6, 8}},});
	}

	public StatsMedianaTest(int expected, int[] input) {
		this.expected = expected;
		this.input = input;
	}

	@Test
	public void testMediana() {
		assertEquals(expected, sut.mediana(input));
	}
}

Ahora podemos crear una nueva clase llamada StatsTest o bien, directamente, StatsSuite. En este caso dará igual y contendrá una suite; JUnit se encargará de encontrar los tests que tenga dentro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// file src/test/java/org/magmax/stats/StatsTest.java
package org.magmax.stats;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;


@RunWith(Suite.class)
@Suite.SuiteClasses({org.magmax.stats.StatsMedianaTest.class})
public class StatsTest {

}

Como podéis comprobar, está vacía. Todo lo que necesitamos se lo estamos dando en una anotación.

Sin embargo estamos como al principio: no hemos aportado nada. Pero ahora nada impide que creemos una nueva clase y la añadamos a nuestra suite:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// file src/test/java/org/magmax/stats/StatsTest.java
package org.magmax.stats;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;


@RunWith(Suite.class)
@Suite.SuiteClasses({org.magmax.stats.StatsMedianaTest.class,
                     org.magmax.stats.StatsMediaTest.class,
                     })
public class StatsTest {

}

Lo que nos obliga a crear la clase StatsMediaTest. Por no alargarme, la creo directamente con proveedor de datos, ya que se ve claramente que lo voy a necesitar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.magmax.stats;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;

@RunWith(Parameterized.class)
public class StatsMediaTest {

	private Stats sut;
	private int expected;
	private int[] input;

	@Before
	public void setUp() {
		sut = new Stats();
	}

	@Parameterized.Parameters
	public static Collection numbers() {
		return Arrays.asList(new Object[][]{
					{0, new int[]{}},
				});
	}

	public StatsMediaTest(int expected, int[] input) {
		this.expected = expected;
		this.input = input;
	}

	@Test
	public void testMedia() {
		assertEquals(expected, sut.media(input));
	}
}

Lo que nos da un error por no tener el método media. Lo creamos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.magmax.stats;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;

@RunWith(Parameterized.class)
public class StatsMediaTest {

	private Stats sut;
	private int expected;
	private int[] input;

	@Before
	public void setUp() {
		sut = new Stats();
	}

	@Parameterized.Parameters
	public static Collection numbers() {
		return Arrays.asList(new Object[][]{
					{0, new int[]{}},
				});
	}

	public StatsMediaTest(int expected, int[] input) {
		this.expected = expected;
		this.input = input;
	}

	@Test
	public void testMedia() {
		assertEquals(expected, sut.media(input));
	}
}

Y volvemos a tener verde.

Terminando lo que hemos empezado

Como todo está explicado, voy directamente con el resultado. Primero el test, claro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package org.magmax.stats;

import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.junit.Assert.*;

@RunWith(Parameterized.class)
public class StatsMediaTest {

	private Stats sut;
	private int expected;
	private int[] input;

	@Before
	public void setUp() {
		sut = new Stats();
	}

	@Parameterized.Parameters
	public static Collection numbers() {
		return Arrays.asList(new Object[][]{
					{0, new int[]{}},
					{5, new int[]{5}},
					{3, new int[]{1, 3, 5}},
					{5, new int[]{2, 4, 6, 8}},
				});
	}

	public StatsMediaTest(int expected, int[] input) {
		this.expected = expected;
		this.input = input;
	}

	@Test
	public void testMedia() {
		assertEquals(expected, sut.media(input));
	}
}

y ahora el código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.magmax.stats;

public class Stats {

	public int mediana(int[] v) {
		if (v.length == 0) {
			return 0;
		}
		return v[getMiddlePosition(v.length)];
	}

	public int media(int[] v) {
		if (v.length == 0) {
			return 0;
		}
		return suma(v)/v.length;
	}

	private int getMiddlePosition(int length) {
		return (int) (length / 2.00001);
	}

	private int suma(int[] v) {
		int result = 0;
		for (int x : v)
			result += x;
		return result;
	}
}

Y nuevamente he necesitado una función auxiliar :D