Qué es un SUT, fake, stub, mock y spy en las pruebas con un ejemplo

Escrito por el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

Las pruebas unitarias y de integración tratan de probar que el comportamiento del sujeto bajo prueba es el esperado. Este sujeto bajo prueba usará colaboradores que en las pruebas deben ser reemplazados por fakes para probar las condiciones deseadas del de sujeto bajo prueba. Dependiendo del comportamiento asignado al colaborador tenemos varios tipos: stub, mock o spy.

Al desarrollar pruebas unitarias o de integración se emplean varios términos que no son imprescindibles conocer para hacer una prueba pero si es recomendable conocerlos para saber que diferencias hay entre ellos y saber que posibilidades tenemos para construir la prueba. Los términos básicos son sujeto bajo prueba o subject under test o SUT, fake, mock, stub y spy.

En una prueba el objeto bajo prueba es el elemento del que se quiere probar su comportamiento y que las pruebas verificarán que para los casos de prueba funciona según lo esperado. Dado que raramente el sujeto bajo prueba funciona de forma aislada sino que que se comunica con otros elementos estos colaboradores han de reemplazarse preprogramado su comportamiento, a los colaboradores se les denomina impostores o fakes ya que no son los objetos reales que se usan en el programa en ejecución pero simulan su comportamiento.

Según el tipo de doble o fake que sea un objeto están los siguientes tipos:

  • Stub: es un fake al que se le reprograman sus valores de retorno y se proporciona al objeto bajo prueba teniendo control sobre cuales son los valores que usa el sujeto bajo control.
  • Mock: es un fake que después de terminada la prueba puede ser examinado para comprobar si las interacciones con el sujeto bajo prueba han sido las correctas, se puede probar si un método ha sido llamado o cuántas veces ha sido llamado junto con sus parámetros. Adicionalmente al igual que los stub puede preprogramarse su comportamiento. Si solo se necesita preprogramar los resultados se suele utilizar un stub y solo cuando además se necesitan verificar las interacciones se usa un mock.
  • Spy: es un fake en el que solo un subconjunto de métodos son fake y a menos que explícitamente sean mockeados el resto de métodos son los reales.

Los colaboradores permiten hacer los casos de prueba deterministas haciendo que siempre produzcan el mismo resultado y las pruebas no se vean afectados por condiciones externas, los colaboradores evitan efectos colaterales, evitan depender del entorno de computación, hacen que las pruebas sean rápidas por no necesitar de sistemas externos como una base de datos o servicio de red y permiten probar comportamientos inusuales en un entorno real.

En el ejemplo se quiere probar un sistema de alarma que cuando detecte en tres mediciones consecutivas que la temperatura está por encima de cierto valor suene una alarma. Los elementos de este sistema serán una clase Monitor que obtiene las temperaturas de un sensor y si detecta la condición de una temperatura elevada hace sonar una alarma. El monitor será el sujeto bajo prueba y el sensor y la alarma los colaboradores. El sensor será un fake de tipo stub ya que solo se necesita preprogramar sus valores de retorno de temperaturas y la alarma un fake de tipo mock ya que se debe comprobar que el monitor ha llamado una vez al método que haría sonar la alarma, esto es se necesitan verificar las interacciones.

Sistema simulado

Sistema de control de temperatura simulado
1
2
3
4
5
6
package io.github.picodotdev.blogbitix.testing;

public interface Sensor {

    int getTemperature();
}
Sensor.java
1
2
3
4
5
6
package io.github.picodotdev.blogbitix.testing;

public interface Alarm {

    void fire();
}
Alarm.java
 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
package io.github.picodotdev.blogbitix.testing;

import java.util.ArrayList;
import java.util.List;

public class Monitor {

    private Sensor sensor;
    private Alarm alarm;
    private int maxTemperature;

    public List<Integer> measurings;

    public Monitor(Sensor sensor, Alarm alarm, int maxTemperature) {
        this.sensor = sensor;
        this.alarm = alarm;
        this.maxTemperature = maxTemperature;

        this.measurings = new ArrayList<>();
    }

    void check() {
        int temperature = sensor.getTemperature();
        if (temperature <= maxTemperature) {
            measurings.clear();
        } else {
            measurings.add(temperature);
        }
        if (measurings.size() >= 3) {
            alarm.fire();
        }
    }
}
Monitor.java
 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
package io.github.picodotdev.blogbitix.testing

import spock.lang.Specification

class MonitorSpec extends Specification {

    def "the monitor fires the alarm on high temp"() {
        given: "a sensor, a alarm and a max temperature"
        def sensor = Stub(Sensor)
        def alarm = Mock(Alarm)
        def maxTemperature = 35

        and: "a monitor"
        def monitor = new Monitor(sensor, alarm, maxTemperature)

        and: "three high temperatures"
        sensor.getTemperature() >>> [42, 45, 49]

        when:
        monitor.check()
        monitor.check()
        monitor.check()

        then:
        1 * alarm.fire()
    }
}
MonitorSpec.groovy

Se pueden hacer más casos de prueba como por ejemplo probar que tras dos temperaturas altas siendo la siguiente baja y posteriormente otra alta la alarma no es disparada.

En el libro Java Testing with Spock explican toda esta teoría básica de pruebas además de explicar bastante detalladamente todas las posibilidades de la herramienta de testing Spock.

Los dobles permiten susituir dependencias y programar su comportamiento, en algunos casos no es posible o no es deseable. Si se quiere probar un componente que accede a una base de datos el doble puede tener diferencias con la base de datos real. Para estos casos se usan pruebas de integración, la dificultad está disponer de estas dependencias en el entorno de prueba. La herramienta Testcontainers sirve para realizar pruebas de integración en Java usando contenedores Docker.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew test

Referencia:


Comparte el artículo: