Nota: Este artículo es una traducción (con permiso) de You never know when the brakes might fail, escrito por Damien Tanner y publicado en New Bamboo

Por si fallan los frenos

Con Rails podemos ir a toda máquina a la hora de desarrollar nuestras aplicaciones, pero con esta velocidad sufrimos un mayor riesgo de descarrilar (no puedo resisitrme a hacer comparaciones ferroviarias). Afortunadamente para nosotros Rails (con todo el cariño de Ruby) tiene un arsenal de pruebas soberbio. Muchos de los que conozco aún no han adoptado la mentalidad del desarrollo orientado a pruebas, lo cual es comprensible porque es una manera totalmente nueva de pensar que lleva cierto tiempo para aprenderla y adaptarse a ella. A principio puede parecer complicado apreciar los méritos de la automatización de pruebas, porque puede significar que tardas el doble de tiempo en construir tu aplicación. Pero algún dia una de tus pruebas fallará, investigarás y descubrirás la causa del fallo y te imaginarás qué habría pasado si ese fallo no hubiera sido descubierto a tiempo. A partir de entonces te darás cuentas, de forma definitiva, de la potencia de las pruebas automáticas.

El uso de pruebas por codigo también te da libertad. Por ejemplo, en un eciente proyecto nuestro hicimos hace un par de semanas ciertos cambios estructurales importantes en la base de datos, y con unos 50 modelos hubiera sido un infierno hacer todo lo que hacía falta sin emplear pruebas unitarias. Una batería completa de tests nos permitió hacer los cambios en la base de datos y ver, inmediatamente, qué modelos de la aplicación necesitaban ser modificados y si las modficiaciones que habíamos hecho eran correctas. En última instancia, las baterias de pruebas me permiten dormir por las noches.

Sorprendentemente, todavía hay pocos tutoriales para ayudarnos a la hora de empezar a escribir nuestras pruebas. Sin embargo, las cosas están cambiando deprisa, por suerte. Esperamos que este artículo te ayuda a familiarizarte con los diferentes tipos de pruebas que se usan con más frecuencia.

Pruebas unitarias

Probar los modelos es vital para que la aplicación funcione. Una batería completa de pruebas untiarias tambien nos da la lbiertad de cambiar los esquemas de la base de datos y la lógica de negocio sin miedo a que se nos cuelen los problemas inadvertidamente. Una vez que le pilles el truco, te preguntarás cómo fué que antes podías vivir sin escribir pruebas unitarias.

Hay diferentes patrones comunes que utilizo a la hora de escribir pruebas unitarias.

Prueba de relación

Es facil cometer errores en las relaciones, especialmente si el esquema de la base de datos evoluciona durante el desarrollo. Cuando uno se encuentra inmerso en un proyecto de envergadura se necesita una bateria completa de pruebas para dormir bien por las noches y por suerte son los mas fáciles de escribir.

Supongamos que en nuestros modelos un Autor tiene muchos Articulos. Esto podría probarse de esta manera:

1
2
3
def test_autor
  assert_equal articulos(:primer_articulo).autor, autores(:damien)
end

Necesitariamos cubrir con pruebas todas las relaciones belongs_to,has_one, has_many y has_and_belongs_to_many en nuestro modelo.

Prueba de creación

En una batería completa de pruebas, nos hará falta tener pruebas de creación, lectura, actualización y borrado. La creación es el sitio donde es mas probable que las cosas vayan mal.

Para comprobar si un registro ha sido creado correctamente, antes nos hacia falta contar el número actual de registros, almacenarlo, y luego volver a contar y comparar después de la invocación del método create. Sin embargo, ahora tenemos un método llamado assert_difference y su contrapartida assert_no_diference. Cuando vi estos metodos por primera vez utilizados en una prueba no me pareció que tuvieran mucho sentido (supongo que era porque por defecto, estos metodos comprueban una diferencia de 1 sin que haga falta que se lo especifiques explícitamente), sin embargo una lectura rápida del código me ayudó a entender el funcionamiento:

1
2
3
4
5
6
7
8
9
10
def assert_difference(object, method = nil, difference = 1)
  initial_value = object.send(method)
  yield
  assert_equal initial_value + difference,
    object.send(method)
end

def assert_no_difference(object, method, &block)
  assert_difference object, method, 0, &block
end

Así, ahora podemos escribir pruebas de creación de objetos de esta guisa:

1
2
3
4
5
def test_create
  assert_difference Articulo, :count do
    autores(:damien).articulos.create(:titulo => 'Segundo articulo', :cuerpo => '...')
  end
end

Lo cual es lo mismo que hacer:

1
2
3
4
5
def test_create
  cuenta_inicial = Articulo.count
  autores(:damien).articles.create(:titulo => 'Segundo articulo', :cuerpo => '...')
  assert_equal cuenta_inicial + 1, Articulo.count
end

Prueba de validación

Después de que ya tengamos nuestras pruebas de creación, querremos extender las validaciones en el modelo. En lugar de montar todos los parámetros a create() en cada prueba, nos será útil añadir un metodo para crear el registro y permitir sobreescribir los parámetros:

1
2
3
4
5
6
7
protected

def create_articulo(opciones = {})
  attrs = { :titulo => 'Mi segundo articulo', :contenido => '...', 
            :contenido_extendido=> 'mas texto' }.merge(opciones)
  Articulo.create(attrs)
end

Así, para validar la presencia o no de atributos de obligada presencia, podriamos hacer:

1
2
3
4
5
6
def test_validates_presence_of_titulo
  assert_no_difference Articulo, :count do 
    nuevo_articulo = create_article({ :titulo => nil })
    assert new_articulo.errors.on(:titulo)
  end
end

Obsérvese que ademas de comprobar que no hay diferencia en el número de articulos en la base de datos (lo cual nos dice que el articulo mal creado no ha sido almacenado en la base de datos), también nos hemos asegurado que se ha asignado un error al atributo titulo. Tambien se podría hacer esta comprobación fuera del bloque assert_difference, pero creo que queda mejor dentro.

Pruebas de acceso

Es posible saltarse las pruebas de lectura de registors, pero si hemos definidos métodos personalizados para leer atributos es posible que queramos añadir pruebas para ellos.

Por ejemplo, en nuestro modelo Articulo hemos añadido un método resumen que devuelve una copia truncada de los contenidos del articulo. Para probarlo, haríamos así:

1
2
3
def test_excerpt
  assert_equal articulos(:mi_primer_post).resumen.length, 100
end

Este es un ejemplo muy simple, pero una vez que empecemos a tratar con la lógica de negocio (y más aun con el dinero de la gente) estaremos obligados a probar los métodos de un modelo.

Prueba de borrado

He aquí mi método favorito para probar la destruccion de registros:

1
2
3
4
5
def test_delete
  assert_difference Articulo, :count, -1 do
    Articulo.destroy(articulos(:mi_primer_post))
  end
end

Pruebas funcionales

Como desarrollador, todos sabemos lo importantes que son nuestros modelos, pero poca gente realmente llega a verlos. A lo que realmente prestan atencino nuestors usuarios es a los controladores y las vistas. Si no estamos escribiendo pruebas funcionales probablement nuestros clientes o usuarios están acostumbrados a ver de vez en cuando los errores de tipo ApplicationError. Sin embargo hay esperanza. Si escribimos una bateria completa de pruebas funcionales, estos errores de aplicación serán cosa del pasado.

Prueba de éxito

La prueba mas básica que nos ahorrara sufrir errores de aplicación será la prueba de éxito. Lo que hacemos es requerir una acción del controlador y asegurarnos que la respuesta HTTP es OK. Y ya que estamos podemos asegurarnos que de que se muestra la vista correcta y que están bien construidas las variables que hacen falta en la vista:

1
2
3
4
5
6
def test_show
  get :show, :id => 1
  assert_response :success
  assert_template 'show'
  assert assigns(:articulo)
end

Con esta prueba nos aseguramos que visitar /articulos/show/1 mostrará la página utilizando la plantilla articulos/show.rhtml y que la variable @@articulo tendrá un valor que podrá ser usado por la vista.

Prueba de redirección

Las acciones que se invoquen en el área de usuarios o de administración de nuestra aplicación deberían redirigirnos a otra página si no estamos registrados. Para comprobar que lo hacemos bien podremos emplear assert_redirected_to

1
2
3
4
5
def test_destroy_requires_admin_priv
  get :destroy, :id => 1
  assert_equal 'Solo administradores', flash[:notice]
  assert_redirected_to '/admin/login'
end

En este caso tambien nos hemos asegurado de que mostramos el mensaje adecuado.

Las pruebas CRUD

Igual que con las pruebas unitarias de los modelos, querremos comprobar las acciones de creación, acceso, actualización y borrado (CRUD, de Create, Read, Update, Delete). De nuevo utilizaremos assert_difference:

1
2
3
4
5
6
def test_destroy
  assert_difference Articulo, :count do
    login_as_admin
    get :destroy, :id => 1
  end
end

Tambien querremos comprobar el envio de formularios como cuando se crean articulos nuevos en un blog:

1
2
3
4
5
6
def test_create
  assert_difference Articulo, :count do
    login_as_admin
    post :new, { :titulo => 'Post de prueba', :cuerpo => 'Texto de artículo' }
  end
end

Pruebas de etiquetas

Si pudiésemos probar las etiquetas de marcado HTML que mostraremos al usuario, estaremos probando exactamente lo que el usuario verá. Siempre se pueden probar los títulos importantes y los enlaces entre páginas.

Uno de los metodos más útiles en las pruebas funcionales es assert_tag, que buscará una etiqueta específica en el marcado HTML devuelto por la aplicación. Han aparecido recientmente dos alternativas a este metodo: Hpricot y sus métodos de pruebas (también hay disponible un plugin) y assert_select que también permite usar selectores CSS para buscar etiquetas concretas. No cabe duda de que estos dos plugins prometen mucho, peor por ahora considero que assert_tag es adecuado en la mayoría de las ocasiones.

En este ejemplo, nos aseguraremos que la página de listado de todos los articulos tiene los títulos correctos con enlaces al pagína de mostrar cada artículo:

1
2
3
4
5
6
7
8
def test_titulos_listado
  get :list
  articulos.each do |articulo|
    assert_tag :tag => 'a',
               :attributes => { :href => "/articles/show/#{articulo.id}" }, 
               :child => { :tag => 'h2', :content => articulo.titulo }
  end
end

Una lectura rápida de la documentación de assert_tag nos ayudará a entender mejor su uso.

Aún no las tengo todas conmigo

Uno de los libros mas útiles que tengo es Practices of an Agile Developer. Si alguna vez tengo mis dudas o vacilo en algún proyecto particular, abro el libro y leo algunos apartados. Todas las practicas son concisas y le levantarán el ánimo incluso al desarrollador mas ignorante. Algunas de las prácticas hacen referencias al desarrollo guiado por pruebas y las herramientas asociadas.

Sorry, comments are closed for this article.