Skip navigation.
Home Home

Pruebas unitarias y de persistencia en Ruby on Rails

Pruebas
Pruebas

Uno de los aspectos que me ha sorprendido más gratamente de Ruby on Rails es la facilidad para hacer pruebas de una aplicación web a todos los niveles.

En el entorno J2EE no he acabado de encontrar herramientas de pruebas que cubrieran todas las necesidades del desarrollo de aplicaciones web. Para pruebas unitarias JUnit o sus competidores están muy bien, pero cuando te sales de ese terreno las cosas no están tan claras. En particular los entornos para llevar a cabo pruebas funcionales provocan que estas supongan un coste considerable.

En Rails tenemos el equivalente a JUnit+dbUnit+HttpUnit+Cactus+Jameleon y todo ello perfectamente integrado. Así da gusto :) El único pero que le pongo son los nombres que han dado a los distintos tipos de pruebas y que resultan bastante engañosos.

En esta entrada del blog comienzo explicando las pruebas que Rails denomina unitarias y que incluyen tanto a las realmente unitarias como a las pruebas de las clases de modelo Active Record. Estas últimas no son puramente unitarias, dado que dependen de que esté disponible una base de datos con determinada información en ella.

Pruebas de presistencia con Fixtures

Las primeras no tienen ningún misterio para las que hayan usado JUnit así que centrémonos en las segundas. Para ellas, Rails proporciona el sistema de Fixtures que permite definir los datos que deben estar presentes en la base de datos antes de la ejecución de la prueba. La forma de definir estos datos es mediante un fichero con formato YAML por cada clase del modelo. Todos estos ficheros se almacenan en el directorio tests/fixtures. Por ejemplo en SprintTracker los ficheros tests/fixtures/sprints.yml contiene:

three_day_sprint:
  id: 1
  project_id: 1
  duration: 3
  goal: 'three_day_sprint'
  start_date: <%= Time.now.to_s :db %>
sprint_with_no_tasks:
  id: 2
  project_id: 1
  duration: 3
  goal: 'sprint_with_no_tasks'
  start_date: <%= 1.day.from_now.to_s :db %>

Y el fichero tests/fixtures/tasks.yml contiene:

finished_task:
  id: 1
  name: "finished_task"
  sprint_id: 1
  initial_estimate: 19
  user_id: 1
started_task:
  id: 2
  name: "started_task"
  sprint_id: 1
  initial_estimate: 23
  user_id: 1
not_started_task:
  id: 3
  name: "not_started_task"
  initial_estimate: 20
  sprint_id: 1
  user_id: 1

El formato es bastante autoexplicativo. Es muy útil poder usar scriplet dentro de este fichero. Además de la forma en que se usa en el primero de los ficheros mostrados otro uso muy útil consiste en usar bucles para crear un varias entradas sin tener que especificarlas de una en una.

Rails se encarga de asegurar que antes de ejecutar cada método de prueba en la tabla de sprints se encuentran las entradas especificadas en el fichero YAML y sólo esas. De esta forma pueden escribirse métodos de prueba como este:

  def test_effort_left_on_started
    task = tasks('started_task')
    assert_equal @started_task_day_2.reestimation, task.effort_left(2)
  end

Es interesante fijarse el el campo de clase @started_task_day_2. Cuando he usado herramientas similares en Java el problema con que me encontraba es que en las código del método de prueba tenía que saber los valores que se habían usado al definir la fixture. Rails consigue evitar esta duplicación ofreciendo la posibilidad de acceder al fichero YAML a través de campos de la clase que tienen el mismo nombre que el que se dio en dicho fichero. Para conseguir esta magia en la clase que define el método usé:

class TaskTest < Test::Unit::TestCase
  fixtures :tasks
  self.use_instantiated_fixtures = true
  ...

Y no hay mucho más que explicar. Rails hace tan sencillo popular la base de datos que puede olvidarse de la diferencia entre lo que realmente son pruebas unitarias y lo que no y así dedicar el tiempo a hacer un tipo de pruebas realmente útiles para el desarrollador de aplicaciones web.

Algunas dificultades

Debo confesar que no todo fue tan bonito. Al usar el sistema de fixtures para hacer pruebas de SprintTracker me encontré con un problema raro que me hizo perder bastante tiempo. Este ha sido uno de los pocos problemas que tampoco mostraban un mensaje con la causa del error suficientemente bien explicada por lo que me costó averiguar la solución.

La conclusión que saqué es que el problema estaba derivado de la relación existente entre las distintas clases del modelo de SprintTracker (proyectos, sprints, usuarios, tareas y estimaciones). Si, al hacer pruebas de una clase del modelo, no cargaba los datos del resto (que de hecho no tenía preparados aún) ocurrían efectos extraños.

la solución afortunadamente fue sencilla. Preparé fixtures para todas las clases del modelo e incluí todos ellos en cada una de las clases de prueba:

  fixtures :estimates, :tasks, :sprints, :projects, :users

Más pruebas

Si las pruebas de persistencia son útiles, mejor aún son las pruebas funcionales y las nuevas pruebas de integración introducidas en Rails 1.1. Pero eso lo dejo para la siguiente entrada del Blog.

Joder....

Cómo está el spam, haced algo!

Magnífico artículo, llevaba tiempo buscando algo similar sobre pruebas en Rails. Gracias!