# Fundamento 5. Testeabilidad

## El test como consumidor

Un test funcional representa a un consumidor del módulo. Entra por su API y comprueba resultados, estado o mensajes que pertenecen al contrato.

La caja negra no tiene que abarcar todo el sistema. Puede ser un módulo pequeño siempre que la prueba respete su límite y no convierta la coordinación interna en una promesa pública.

```text
test → API → implementación
                  └── API consumida → colaborador
```

La pregunta principal es qué debe seguir siendo cierto para el consumidor. La elección entre implementación real, doble de prueba o integración viene después.

## Qué puede observar una prueba

Un contrato ofrece tres clases de observación:

| Observación | Ejemplo genérico |
|---|---|
| Resultado directo | Valor o error devuelto |
| Estado accesible por la API | Una consulta posterior refleja el cambio |
| Mensaje saliente | Se publica un evento o se solicita un efecto |

Los helpers utilizados, el reparto entre objetos internos y el algoritmo elegido quedan fuera. También quedan fuera el número y el orden de llamadas cuando no cambian el resultado observable.

Una prueba funcional debería aceptar dos implementaciones que produzcan los mismos resultados, el mismo estado visible y los mismos mensajes contractuales.

## Ejemplo: conservar un valor ante un fallo

Consideremos un módulo de caché:

```text
Cache.Refresh(Key) → Value | SourceUnavailable
Cache.Get(Key)     → Value | NotFound
```

`Cache` consume otra API:

```text
Source.Load(Key) → Value | error
```

Queremos demostrar una sola garantía: si la fuente falla durante una actualización, el valor anterior continúa disponible.

El test ejecuta la implementación real de `Cache` y prepara un doble de `Source` que devuelve un fallo. Después observa únicamente la API:

```text
dado un valor almacenado para una clave
cuando Refresh recibe SourceUnavailable
entonces devuelve SourceUnavailable
y Get conserva el valor anterior
```

La prueba no necesita saber si la caché escribe primero en una copia, usa un bloqueo o revierte una asignación. Tampoco necesita una fuente remota real, porque el riesgo observado es la reacción del módulo al fallo, no el protocolo de red.

Una prueba diferente debería comprobar el adaptador real si el riesgo estuviera en la serialización, la configuración o el transporte.

## Ejemplo en Go

Suponiendo la API anterior, el caso puede escribirse sin observar ninguna llamada interna.

```go
import (
    "errors"
    "testing"
)

type sourceStub struct{ err error }

func (s sourceStub) Load(string) (string, error) {
    return "", s.err
}

func TestRefreshKeepsPreviousValue(t *testing.T) {
    cache := NewCache(sourceStub{err: ErrSourceUnavailable})
    cache.Put("key", "previous")

    _, err := cache.Refresh("key")
    if !errors.Is(err, ErrSourceUnavailable) {
        t.Fatalf("expected source error, got %v", err)
    }

    got, _ := cache.Get("key")
    if got != "previous" {
        t.Fatalf("expected previous value, got %q", got)
    }
}
```

El test controla una entrada indirecta mediante un stub y comprueba resultado y estado. No exige cuántos helpers debe ejecutar la caché.

## Hablar con el vocabulario habitual

**Doble de prueba** es el término general para cualquier sustitución usada durante una prueba. Dentro de esa familia conviene mantener los significados conocidos, en lugar de redefinir `fake` para abarcarlo todo.

| Término | Uso |
|---|---|
| Implementación real | La misma implementación usada fuera del test |
| Fake | Implementación funcional simplificada, como un almacenamiento en memoria |
| Stub | Devuelve respuestas preparadas para controlar una entrada indirecta |
| Spy | Registra mensajes para poder observarlos después |
| Mock | Declara y verifica expectativas sobre interacciones |

La terminología procede de la taxonomía recogida por Gerard Meszaros y resumida por Martin Fowler en [*Test Double*](https://martinfowler.com/bliki/TestDouble.html). Saber el nombre ayuda, pero la decisión importante sigue siendo qué comportamiento sustituye el doble y qué riesgo deja sin cubrir.

En el ejemplo anterior basta un stub de `Source`: prepara el fallo que el caso necesita. Un fake funcional sería útil si muchos tests necesitaran una fuente en memoria con reglas estables. Un mock solo tendría sentido si una interacción concreta formara parte del contrato.

## Elegir colaboradores

La implementación real ofrece la mayor fidelidad y es la primera opción cuando resulta rápida, determinista, hermética, segura y fácil de construir. La guía de [*Software Engineering at Google* sobre test doubles](https://abseil.io/resources/swe-book/html/ch13.html) propone el mismo punto de partida pragmático.

| Situación | Elección habitual |
|---|---|
| Colaborador rápido y determinista | Implementación real |
| Muchos casos necesitan semántica estable sin infraestructura | Fake funcional |
| Un caso necesita una respuesta excepcional | Stub local |
| El contrato incluye un mensaje saliente | Spy o mock |
| El riesgo depende de protocolo, transacción o configuración | Integración con el adaptador real |

No es necesario sustituir valores, entidades o funciones puras solo porque colaboren en el caso. Tampoco hace falta levantar infraestructura real para demostrar una regla que no depende de ella.

## Cuándo observar interacciones

Un mensaje saliente puede formar parte del contrato. En ese caso un spy o un mock permite observar su contenido, cantidad u orden.

La expectativa debe limitarse a la diferencia funcional:

- contenido, si otro consumidor depende de esos campos;
- cantidad, si duplicar u omitir el mensaje cambia el efecto externo;
- orden, si el protocolo lo exige.

Comprobar que se llamó a un mapper, una query concreta o un helper privado congela la implementación. Comprobar que se publicó un mensaje requerido protege el contrato.

Una interacción solo demuestra que el mensaje se intentó enviar. No demuestra que el receptor real lo acepte. Cuando esa compatibilidad es el riesgo, hace falta una prueba del adaptador o una integración.

## Qué debe conservar un fake

Un fake funcional implementa un subconjunto declarado del contrato. Debe:

- aceptar las mismas entradas en los casos soportados;
- producir resultados, errores y estado compatibles;
- ser determinista y aislar el estado de cada test;
- rechazar de forma visible las capacidades que no implementa;
- documentar las propiedades que omite.

No necesita copiar la infraestructura. Una fuente en memoria puede reproducir lectura, ausencia y versiones sin simular red o latencia. Si el caso trata precisamente de esas propiedades omitidas, el fake deja de ser una prueba suficiente.

Un fake compartido acumula responsabilidad y merece tests propios. Cuando sea viable, una misma suite de contrato puede ejecutarse contra el fake y el adaptador real para detectar divergencias.

## Método de revisión

Para diseñar una prueba:

1. Nombra el módulo, la API consumida y la garantía que se quiere proteger.
2. Elige una observación pública: resultado, estado o mensaje.
3. Retira afirmaciones sobre coordinación interna.
4. Ejecuta colaboradores reales mientras sean prácticos.
5. Introduce el doble más pequeño que permita controlar u observar el caso.
6. Declara qué fidelidad falta y qué riesgo necesita integración.
7. Comprueba que otra implementación correcta también pasaría.

> **Primero decide qué debe observarse; después elige con qué implementaciones puede demostrarse.**
