Del zeitgeist de la pasada Conferencia Rails me quedaría con la idea de que el testing de nuestra aplicación es, no ya posible, sino inexcusable. Así que llevo unos días dándole vueltas a las herramientas de testing que ofrece Rails, más allá de las conocidas técnicas de pruebas unitarias de las que ya hemos hablado por aquí alguna vez
Así que hoy trataré de indagar un poco qué posibilidades tenemos para probar las vistas, que es un territorio más pastoso y maleable que el mundo de los modelos, más
predecibles y modosos. Hasta ahora la gran herramienta disponible para verificar aserciones sobre el HTML generado por nuestra aplicación era assert_tag,
pero de la mano de Assaf Harkin, ha llegado un nuevo pistolero a la ciudad llamado
assert_select que de hecho ha acabado entrando en el core de Rails) (si bien también está disponible como plugin).
Pruebas del HTML para llamadas GET y POST estándar
Para introducir a este nuevo amiguito vamos a jugar con la clásica aplicación de libros y autores. Tendremos, pues, una base de datos con esta pinta:
1 2 3 4 5 6 7 8 9 10 11 |
ActiveRecord::Schema.define(:version => 1) do create_table "authors", :force => true do |t| t.column "name", :string end create_table "books", :force => true do |t| t.column "name", :string t.column "author_id", :integer end end |
No hay sorpresas en nuestros modelos:
1 2 3 4 5 6 7 |
class Author < ActiveRecord::Base has_many :books end class Book < ActiveRecord::Base belongs_to :author end |
Crearemos un andamiaje con script/generate scaffold Author y ya podemos empezar a jugar con la aplicación con script/server. Si entramos en el controlador authors, podemos dar de alta un par de autores y ver lo que nos devuelve la acción index:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<body> <h1> Listing authors </h1> <table> <tr><th>Name</th></tr> <tr> <td>Isaac Asimov</td> <td><a href="/authors/show/1">Show</a></td> <td><a href="/authors/edit/1">Edit</a></td> </tr> <tr> <td>Carl Sagan</td> <td><a href="/authors/show/2">Show</a></td> <td><a href="/authors/edit/2">Edit</a></td> </tr> (etcétera) </table> </body> |
¿Podemos escribir una prueba que nos asegure que la tabla se está construyendo de esta manera y no de otra? La respuesta es que sí. Para ello, necesitamos crear una
fixture para nuestros tests con los datos de la tabla de autores que vamos a considerar en nuestras pruebas, por ejemplo:
1 2 3 4 5 6 7 8 9 |
first:
id: 1
name: Carl Sagan
another:
id: 2
name: Isaac Asimov
last:
id: 3
name: Arthur Clarke |
Y ahora entra en acción assert_select. En nuestro archivo con las pruebas funcionales para el controlador de autores test/functional/authors_controller_test.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def test_list get :list assert_response :success assert_template 'list' assert_select "table" do assert_select "tr>th", "Name" assert_select "tr:nth-child(2)>td", "Carl Sagan" assert_select "tr:nth-child(3)>td", "Isaac Asimov" assert_select "tr:nth-child(4)>td", "Arthur Clarke" end end |
Con el primer assert_select nos estamos quedando con el primer elemento table del HTML devuelto al invocar la acción list de nuestro controlador. En el bloque pasado como parámetro las siguientes assert_select actuarán sobre elementos HTML que cuelguen del tag table.
La sintaxis de de los selectores que escogen nodos del árbol de etiquetas de la página no es precisamente sencilla, pero cubre todos los casos posibles. Teneis un chuletario aquí
Selectores CSS
La mayor funcionalidad que nos da assert_select es que que tenemos a nuestra disposición todos los selectores CSS, con lo que quizá podríamos escribir nuestra prueba de
una manera más sencilla (y con un ejemplo más parecido al Mundo Real):
1 2 3 |
<% for column in Author.content_columns %> <td class="author_name"><%=h author.send(column.name) %></td> <% end %> |
Ahora podemos afinar la selección sin tener que buscar directamente el tag de la tabla.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def test_list_with_css get :list assert_response :success assert_template 'list' autores = ["Carl Sagan", "Isaac Asimov", "Arthur Clarke"] assert_select ".author_name" do autores.each do |name| assert_select "td", name end end end |
Rizando el rizo, también podríamos escribir esto tomando directamente todas las fixtures sin necesidad de tener en una lista los nombres duplicados:
1 2 3 4 5 6 7 8 9 10 11 12 |
def test_list_with_css_no_array get :list assert_response :success assert_template 'list' assert_select ".author_name" do Author.find(:all).each do |author| assert_select "td", author.name end end end |
Con assert_select podemos proteger nuestro código Rails de cambios indeseados (o inesperados) en la plantilla (si por error ponemos .autor_name en la plantilla list.rhtml nuestros tests empezarán a fallar indicándonos que algo va mal antes de que nos llame un cliente preguntando por qué con el último cambio en producción se han dejado de ver los nombres de los clientes en color azul-lapislázuli).
¿Y qué pasa con las llamadas Ajax?
No hay problema, select_tag nos permite también cubrirnos las espaldas. Supongamos que en nuestra vista tenemos ahora un enlace al método books que nos devuelve una lista de los libros para un autor dado, actualizando de manera asíncrona un div en nuestra plantilla:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<% for author in @authors %> <tr> <% for column in Author.content_columns %> <td class="author_name"><%=h author.send(column.name) %></td> <% end %> <td><%= link_to 'Show', :action => 'show', :id => author %></td> <td><%= link_to 'Edit', :action => 'edit', :id => author %></td> <td><%= link_to 'Destroy', { :action => 'destroy', :id => author }, :confirm => 'Are you sure?', :method => "post" %></td> <td><%= link_to_remote 'Show books', :url => {:action => 'books', :id => author} %></td> </tr> <% end %> <div libros> </div> |
La acción books del controlador AuthorController, es tan trivial como
1 2 3 |
def books @books = Author.find(params[:id]).books end |
Con la plantilla books.rjs
page.replace_html 'libros', :partial => 'book', :collection => @books |
Y el parcial _book.rhtml:
<p class="book"><%= book.name %></p> |
Creando el fichero de fixtures correspondientes e incluyéndolas con :fixtures 'books' en nuestro authors_controller_test (ojo con este paso que si se olvida para Rails sería como si no hubiésemos definido ningún fixture para nuestras pruebas), pasamos a escribir nuestra prueba:
1 2 3 4 5 6 7 8 9 10 11 |
def test_books xhr :get, :books, :id => 1 assert_select_rjs "libros" do assert_select ".book" do assert_select "p", "Cosmos" assert_select "p", "Contact" assert_select "p", "Broca's Brain" end end end |
Simplemente invocamos assert_select_rjs, indicándole el selector de la parte que se va actualizar con nuestra llamada Ajax (si no se actualizase el HTML del div con id libros, la primera aserción fallaría). Tras eso, podemos proceder que en el caso no-Ajax.


Leave a Reply