# Rails sostenible (VII): testing y un ejemplo end-to-end

*Séptima entrega de la serie **[Rails sostenible](/search?tag=rails-sostenible)**, sobre el libro de David Bryant Copeland. Tiempo de lectura estimado: 14 minutos.*

El testing es donde más fácil se peca por exceso y por defecto. Copeland no predica el "testéalo todo" ni el "los tests frenan", sino algo más maduro: los tests son una **inversión con coste**, y la sostenibilidad consiste en maximizar el valor que dan por el coste que tienen. En este post junto el capítulo sobre testear la vista con el ejemplo *end-to-end*, porque juntos enseñan el flujo de trabajo completo que propone el libro.

## El valor y el coste de los tests

Todo test tiene **valor**: detecta regresiones, documenta comportamiento, te da confianza para cambiar. Pero todo test tiene **coste**: hay que escribirlo, mantenerlo cuando el código cambia, y ralentiza la suite. Un test sostenible es el que da mucho valor por poco coste. Uno insostenible es el que se rompe cada vez que tocas un detalle de implementación sin que nada se haya roto de verdad —esos tests frágiles que acabas borrando o, peor, ignorando.

> No preguntes "¿esto tiene tests?", pregunta "¿estos tests me dan confianza sin frenarme?". Cobertura alta con tests frágiles es una falsa sensación de seguridad.

De aquí salen las dos grandes decisiones: **qué nivel de test** usar para cada cosa, y **cómo escribirlos** para que no sean frágiles.

## rack_test para flujos sin JavaScript

Para la mayoría de flujos de usuario —los que no dependen de JavaScript— Copeland recomienda los system tests con el driver **`:rack_test`**: simulan un navegador a nivel de HTTP, sin arrancar un navegador real. Son rápidos, deterministas y suficientes para verificar que un formulario se envía, que aparece el contenido correcto, que una redirección lleva donde debe.

```ruby
# test/system/customer_signup_test.rb
require "application_system_test_case"

class CustomerSignupTest < ApplicationSystemTestCase
  driven_by :rack_test   # rápido, sin navegador real

  test "un visitante puede registrarse" do
    visit new_customer_path
    fill_in "Nombre", with: "Ada Lovelace"
    fill_in "Email", with: "ada@example.com"
    click_on "Crear cuenta"

    assert_text "Bienvenida, Ada"
  end
end
```

Reservas el navegador real (Selenium, Cuprite) **solo para los flujos que de verdad necesitan JavaScript**. Esa fue una de las conclusiones de [la quinta entrega](/post/rails-sostenible-v-helpers-css-y-minimizar-javascript): si una funcionalidad depende de JS, su system test debe correr en un navegador de verdad, de modo que si el JavaScript se rompe, el test falle. Pero esos tests son lentos y frágiles, así que cuantos menos, mejor.

![Resaltado de sintaxis de Ruby en el editor](fig-01.webp)

## data-testid contra los tests frágiles

Aquí está el truco más práctico del capítulo. ¿Por qué se rompen tantos system tests? Porque seleccionan elementos por cosas que **cambian por motivos de presentación**: el texto exacto de un botón, una clase CSS, la estructura del DOM. Cambias "Crear cuenta" por "Registrarme" y veinte tests se ponen rojos sin que nada funcional se haya roto.

La solución de Copeland: usar atributos **`data-testid`** como puntos de anclaje estables, desacoplados del estilo y del texto:

```erb
<button data-testid="signup-submit" class="btn btn--primary">
  Crear cuenta
</button>
```

```ruby
find("[data-testid='signup-submit']").click
```

Ahora el diseñador puede cambiar el texto, las clases y el layout cuanto quiera: mientras el `data-testid` siga ahí, el test aguanta. Separas lo que el test necesita (un punto estable de interacción) de lo que cambia constantemente (la presentación). Es la misma filosofía de minimizar el coste de mantenimiento que recorre todo el libro.

## Herramientas de diagnóstico explícitas

Copeland insiste en algo que ahorra horas: cultivar **herramientas de diagnóstico** para cuando un test falle. Capturas de pantalla automáticas al fallar (Rails las hace de serie en system tests), volcados del HTML, helpers propios que impriman el estado relevante. Un test que falla con "no encontré el elemento" sin más contexto cuesta una tarde; uno que te deja una captura y el HTML del momento, cinco minutos.

## Falsea el back-end para sacar el system test adelante

Una técnica clave del flujo end-to-end: para que un system test pase **antes** de tener la lógica de negocio implementada, puedes **falsear el back-end**. Construyes la UI, escribes el test que la dirige, y haces que el controlador devuelva datos de pinta o un servicio falso. El test verde te confirma que la UI y el flujo están bien antes de invertir en la lógica real. Esto encaja con el seam: como la lógica vive detrás de una costura, es fácil sustituirla por un doble mientras desarrollas la interfaz.

## El ejemplo end-to-end, paso a paso

El capítulo diecisiete es oro porque une todo lo de la serie en un flujo de trabajo concreto. Copeland desarrolla una feature completa siguiendo este orden:

1. **Define los requisitos** en lenguaje de negocio. Qué tiene que pasar, no cómo.
2. **Construye la UI primero.** El HTML y la pantalla, con datos falsos si hace falta. Empezar por la interfaz te obliga a entender qué necesita el usuario antes de diseñar la trastienda.
3. **Escribe un system test** que recorra el flujo de usuario de principio a fin. Con `rack_test` si no hay JS. Este test es tu red de seguridad de alto nivel.
4. **Esboza la lógica de negocio y define el seam.** Decides qué operación del dominio hace falta y cuál es su interfaz (el nombre de clase y de método explícitos de [la tercera entrega](/post/rails-sostenible-iii-logica-de-negocio-fuera-de-active-record)), aún sin implementarla del todo.
5. **Implementa y testea la lógica de negocio** con tests unitarios rápidos y enfocados. Como vive fuera de Rails, estos tests no necesitan la base de datos ni el framework para casos puros: son veloces.
6. **Conecta todo** y mira el system test ponerse verde de verdad, ya sin falsos.
7. **Reflexiona** sobre lo construido: ¿está la lógica donde toca? ¿los tests dan confianza sin ser frágiles?

```ruby
# Test unitario de la lógica de negocio: rápido y enfocado
class CancellationsTest < ActiveSupport::TestCase
  test "cancelar un pedido pagado genera un reembolso" do
    order  = orders(:paid)
    result = Orders.new.cancel(order)

    assert result.cancelled?
    assert result.refund.present?
  end
end
```

Lo bonito de este flujo es cómo reparte el esfuerzo de testeo: **un** system test de alto nivel que verifica que el usuario puede hacer la cosa, y **muchos** tests unitarios rápidos sobre la lógica de negocio donde viven los casos límite. Pocos tests lentos y caros arriba; muchos tests baratos y veloces abajo, donde está la complejidad real.

## Mi versión

El framing de "valor vs coste" me parece la mejor vacuna contra las dos enfermedades del testing. Contra el dogmático del 100% de cobertura, que llena la suite de tests frágiles que verifican detalles de implementación y que el equipo acaba ignorando. Y contra el que dice "los tests me frenan", que normalmente ha sufrido justo esos tests frágiles y ha sacado la conclusión equivocada.

Lo de `data-testid` lo adopté hace tiempo y no he mirado atrás. Antes, cada rediseño me rompía media suite de system tests por cambios cosméticos. Con anclas estables, el diseño y los tests viven en paz. Es de esos trucos baratos con un retorno enorme.

Y el flujo end-to-end —UI primero, system test, seam, lógica con sus unitarios, conectar— es prácticamente como trabajo cuando la feature es relevante. Empezar por la pantalla me obliga a entender el problema desde el usuario, y tener el seam definido me deja avanzar en la UI con un back-end falso sin bloquearme. Pocos tests caros arriba, muchos baratos abajo: esa pirámide, bien entendida, es pura sostenibilidad.

## Lo que viene

Con el testing encarrilado, volvemos a las capas de Rails que conectan el framework con el mundo. En el **próximo post** toca el lado "frontera" del seam: **controladores, jobs y otras clases frontera**. Veremos por qué un controlador debería ser casi configuración, cómo convertir parámetros en tipos ricos, por qué Sidekiq sigue siendo la mejor opción de jobs para la mayoría, la importancia de la idempotencia, y el papel de mailers y rake tasks.
