La Coctelera

in web we trust Fernando Blat's blog, developer at La Coctelera and Partigi.

Categoría: Testing

30 Enero 2009

Escrito el: 30 ene 2009 @ 09:02 PM

Categorías: Testing

Tags: testing, mocha

Comentarios:
sin comentarios

compártelo

Testing Twitter notifications

In La Coctelera we offer to the users the possibility to automatically update their Twitter status with a link to the last post or the last published photos (shortly :).

The tests for notifying twitter are very easy if you use the powerful Mocha (in the beginning we tried to simulate the Twitter::Base class, but it gets a little bit more complicated):

   def test_an_activity_of_new_photos_notify_in_twitter
     User.any_instance.stubs(:premium_role?).returns(true)
     User.any_instance.stubs(:twitter_photos?).returns(true)
     User.any_instance.stubs(:twitter_username).returns('wadus')
     User.any_instance.stubs(:twitter_password).returns('wadus')
     photo = photos(:photo_blat_1)
     a = activities(:activity1)
     Twitter::Base.any_instance.expects(:update).with("He subido una nueva foto a La Coctelera: #{photo.guid}").once
     a.notify_in_twitter
   end
 

If you don't know Mocha you should learn to use it, in other case, you can ignore this post :)

Also, I recommend you to watch this old screencast about Mocha

10 Septiembre 2008

Should(a) cache

Related to my last post about testing cache and some curiosity about Shoulda I made a branch of it to add some helper methods for testing cache generation and expiration:

  • should_cache_fragment
  • should_cache_action
  • should_expire_fragment
  • should_not_expire_action
  • ...

The good thing was to investigate how shoulda was organized, the test suite it has and that is so easy to hack.

I submited a ticket that I hope the authors approve in next days.

6 Septiembre 2008

Cache testing in Rails 2

At least this week I had some time to spent in ending a litte small project: adapt and improve the cache_test plugin to Rails 2.x. You can find it at GitHub: cache_test.

Cache test is a very useful plugin that allows you to test if action/fragment/page cache is generated or expired during a request. For me is very surprisingly that Rails hasn't asserts for testing the cache, specially when, if your application uses cache, it can be the cause of its malfunctioning. Apart of that cache is so tedious to debug.

The way it works is very easy:

     assert_cache_fragment({:fragment => 'total_posts'}) do
       get :index, :user_id => users(:first)
       assert_response :success
     end
 

In my plugin I have adapted the cache storage definition to Rails 2.x (moved to ActiveSupport, etc), added more asserts and some tests.

Enjoy it!

More info in the README.

13 Agosto 2007

Trabajando con BackgroundDRB en el entorno de test

BackgrounDRB es un proyecto en desarrollo que pretende crear un servicio estable para ejecutar tareas en segundo plano, sin que estas entorpezcan la navegación del usuario por la aplicación, relentizando sus tiempos de respuesta.

La verdad es que era un completo desconocido para mí hasta que encontré la presentación de Sergio Espeja en la conferencia de Rails: Tareas en background con RoR y BackgrounDRb.

En este año que ha pasado, hay una nueva versión que corrige algunos fallos y deja otros abiertos, pero que es, dicen, bastante estable. Y la verdad es que con la anterior tuve muchos problemas para crear mis workers pero con esta estoy encantado.

Pero este post no era para hablar de las bondades del proyecto, que son muchas, sino para contar cómo hacer que en los tests no necesitemos tener lanzado un servidor BackgroundDRB para que estos funcionen, sino que podemos utilizar un mock.

He encontrado uno entre los tickets del proyecto, y que se ve que aún no está incluído en la última versión.

Basta con descargarlo en test/mocks/test e incluir la siguiente línea en el test_helper.rb:


 require File.dirname(__FILE__) + '/mocks/test/backgroundrb_mock.rb'
 

Y voilá, tests funcionando de nuevo.

12 Agosto 2007

Testeando helpers

El otro día, buscando información sobre cómo testear helpers en Rails di con este post: Test your helpers, en el que el autor habla de un plugin desarrollado por él mismo que facilita sobremanera la labor.

El plugin se llama helper_test y es muy sencillo de utilizar:

  1. descargar
  2. editar tests/test_helper.rb e incluír la línea: require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
  3. utilizar el generador que incluye: ./script/generate helper_test Foo

El test generado tendrá este aspecto:


   require File.dirname(__FILE__) + '/../../test_helper'
 
   class FooHelperTest < HelperTestCase
 
     include FooHelper
 
     #fixtures :users, :articles
 
     def setup
       super
     end
 
   end
 

A destacar que podemos utilizar fixtures.

Y la forma de proceder también es muy sencilla: creamos un test y en su interior podemos invocar al helper como si de un método más se tratara y comparar el resultado obtenido con los valores esperados. Si el helper tiene argumentos, ampliaremos el abanico de pruebas.

Una pequeña reflexión

La verdad es que no he podido pensar mucho sobre la efectividad de este plugin ni tampoco tengo experiencia utilizándolo (de hecho los desarrolladores de The-Shaker estábamos algo preocupados sobre el vacío que teníamos en los helpers de la aplicación, pues no tienen tests). En principio y echando un pequeño vistazo a los helpers que tenemos, parece que sí que se van a poder testear todos utilizando este plugin.

Sin embargo, hay que notar dos cosas:

La primera es que si en tu helper has utilizado una variable de instancia (confiando en que en la vista donde vas a utilizar dicho helper, la variable ya esté instanciada y con valor), debes de asignar una variable de igual nombre antes de invocar al helper en el test. Por ejemplo:


 def available_tabs
   tabs =  "<ul>\n"
   tabs << " <li id=\"tab_blog\">" + link_to(_("Blog"), :controller => 'posts',
            :action => 'index', :blog_nicetitle => @blog.nicetitle) + "</li>\n"
   tabs << "</ul>\n"
 end

Y el test:


 def test_avaible_tabs
   @blog = blogs(:el_blog_de_quentin)
   tabs = available_tabs
   assert_equal tabs, '....'
 end

Y la segunda es más bien una sugerencia: muchas veces los helpers generan HTML, y todos sabemos lo coñazo que puede ser comprobar la estructura del mismo, y las bondades del assert_select. Pues bien, se puede hacer un hack no muy elegante, pero bastante práctico para poder utilizar assert_select con el valor obtenido por un helper. Basta con editar el fichero helper_testcase.rb e incluír la línea:


   @response   = ActionController::TestResponse.new
 

dentro de la función setup. Y ahora, con que asignemos el helper a @response.body ya lo tenemos. Veamos un ejemplo:


 def test_avaible_tabs
   @blog = blogs(:el_blog_de_quentin)
   @response.body = available_tabs
   assert_select 'ul'
 end

Es sucio, poco semántico, y muy DRY, porque lo tienes que asignar tras cada invocación al helper, pero parece que de momento no hay otra forma. ¿Alguien se atreve a redefinir?

12 Agosto 2007

Tests de unidad de ActionMailer

ActionMailer es la librería incluída en Ruby on Rails para gestionar el envío de correos desde el framework.

Su uso es bastante sencillo y cumple su labor perfectamente, a excepción de pequeños detalles que parece que falten por pulir.

Uno de esos "detalles insignificantes" es, para mi humilde opinión, el testing, que, aunque se puede realizar sin problemas, y con una cobertura muy buena de la funcionalidad, no queda tan limpio como queda en otras partes del framework.

De hecho, una de las cosas que más se echa en falta es algo de documentación. Por suerte, en los Manuales de Rails (qué mal suena, ¿no?), encontramos un "libro" sobre testing en Rails, y uno de sus capítulos es testing your Mailers, que es justo lo que queremos hacer.

Ahí proponen un esquema o esqueleto para los tests de unidad tal que así:


 require File.dirname(__FILE__) + '/../test_helper'
 require 'my_mailer'
 
 
 class MyMailerTest < Test::Unit::TestCase
   fixtures :users
   FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
   CHARSET = "utf-8" 
 
   include ActionMailer::Quoting
 
   def setup
     ActionMailer::Base.delivery_method = :test
     ActionMailer::Base.perform_deliveries = true
     ActionMailer::Base.deliveries = []
 
     @expected = TMail::Mail.new
     @expected.set_content_type "text", "plain", { "charset" => CHARSET }
   end
 
   def test_reset_password
     user = User.find(:first)
     newpass = 'newpass'
     response = MyMailer.create_reset_password(user,newpass)
     assert_equal 'Your New Password', response.subject
     assert_match /Dear #{user.full_name},/, response.body
     assert_match /New Password: #{newpass}/, response.body
     assert_equal user.email, response.to[0]
   end
 
   private
     def read_fixture(action)
       IO.readlines("#{FIXTURES_PATH}/community_mailer/#{action}")
     end
 
     def encode(subject)
       quoted_printable(subject, CHARSET)
     end
 end
 

Así un poco por encima, además de los requires pertinentes y de establecer unas cuantas constantes debemos de incluír include ActionMailer::Quoting para poder utilizarlo en los tests con aquellos emails que tengan acentos, eñes o cualquier otro carácter no estándar en el subject.

En el método setup configuramos el ActionMailer y además creamos un objeto TMail llamado @expected, y que utilizaremos en todos los tests para compararlo con el que genera nuestro modelo:


   def setup
     ActionMailer::Base.delivery_method = :test
     ActionMailer::Base.perform_deliveries = true
     ActionMailer::Base.deliveries = []
 
     @expected = TMail::Mail.new
     @expected.set_content_type "text", "plain", { "charset" => CHARSET }
   end
 

Y respecto a las fixturas, básicamente no tenemos, así que nos proponen utilizar ficheros de texto plano cuyo contenido será el contenido del email. Esos ficheros se sitúan en RAIlS_ROOT/test/fixtures/community_mailer/ como se puede ver en la función read_fixture:


   def read_fixture(action)
     IO.readlines("#{FIXTURES_PATH}/community_mailer/#{action}")
   end
 

Nuestro propio test

Con el esquema ya creado podemos empezar a escribir nuestros propios tests sobre nuestros modelos de Mailer.

Por ejemplo, veamos una acción muy simple de La Coctelera, que te envía un email cada vez que alguien te añade como amigo:


 	def new_friend(user,friend)
     @headers['reply-to'] = user.email
     @from = 'admin@domain.com'
     @recipients = friend.email
     @subject = "[La Coctelera] #{user.shortname} te ha añadido como amigo"
     @body = {
       'user_name' => user.name,
       'friend_name' => friend.name,
       'profile_link' => profile_url(user.username),
       'profile_link_friends' => friends_url(friend.username)
     }
   end
 

Y esta es la vista asociada:


 Hola <%= @friend_name %>!
 
 <%= @user_name %> te ha añadido como amig@. Puedes visitar su página en la siguiente
 dirección: <%= @profile_link %>
 
 Puedes ver a todos tus amigos en:
 
 <%= @profile_link_friends %>
 
 Hasta otra!
 
 --
 La Coctelera
 http://www.lacoctelera.com
 

El test podría ser algo tal que así:


 def test_new_friend
   user = User.find_by_username('lucas')
   friend = User.find_by_username('blat')
   @expected.from = APP['adminmaster']
   @expected.subject = encode("[La Coctelera] #{user.shortname} te ha añadido como amigo")
   @expected.body = read_fixture('new_friend')
   @expected.to = friend.email
   @expected.reply_to = user.email
   assert_equal @expected.encoded, Notifier.create_new_friend(user, friend).encoded
 end
 

El procedimiento es muy sencillo:

  • cargamos a los dos usuarios implicados en la acción
  • vamos rellenando los atributos de @expected con los valores que esperamos que tenga el email resultante: remitente, asunto, contenido, destinatario...
  • finalmente, comparamos el email "esperado", con el que genera la acción create_new_friend

Hemos necesitado crear una fixtura new_friend con el siguiente contenido:


 Hola Fernando!
 
 Lucas Nicolás te ha añadido como amig@. Puedes visitar su página en la siguiente dirección: http://test.host/lucas/perfil
 
 Puedes ver a todos tus amigos en:
 
 http://test.host/blat/amigos
 
 Hasta otra!
 
 --
 La Coctelera
 http://test.host
 

Como veis, esta fixtura tiene una limitación más que evidente: no es dinámica, y su contenido depende de los valores (en este caso de los usuarios), que estamos utilizando en el test.

También quiero destacar esta línea:


   @expected.subject = encode("[La Coctelera] #{user.shortname} te ha añadido como amigo")
 

Cuando el subject del email vaya a incluir algún caracter acentuado, o con eñe, como es el caso, hay que forzar la recodificación de la cadena, pues ActionMailer también la recodificará cuando cree el email.

En un post posterior propondremos un par de mejoras para solventar alguna de las deficiencias que hemos visto.

6 Abril 2007

Tests de unidad: testeando relaciones entre modelos

Los tests de unidad son tests sobre el modelo de datos, tanto atributos, como métodos del modelo, como relaciones (que al final todo son métodos, pero bueno).

Si lo que queremos testear es una relación con otro modelo, podemos utilizar combinaciones de asserts.

Si nuestros modelos son:


 class Foo < ActiveRecord::Base
   belongs_to :bar
 end

 class Bar < ActiveRecord::Base
   has_many :foos
 end
 

El test de unidad de Bar podría contener este test sobre la relación:


 def test_foos
   bar = Bar.find(1)
   # testeamos que responde al método :foos
   assert_respond_to bar, :foos
   # testeamos que :foos devuelve un Array
   assert bar.foos.is_a? Array
   # testeamos que :foos es un Array de Foo
   assert !bar.foos.empty?
   assert bar.foos.first.is_a? Foo
 end
 

Es sólo una propuesta, y es artesanal 100%, pues ya se sabe que para esto de los tests no hay reglas fijas, sino experiencias personales sobre qué resulta más efectivo.

Así que se proponen sugerencias.

6 Abril 2007

Testea tus tests

Testear tus tests, vaya redundancia, ¿no? ¡Pues no! Veamos en un ejemplo a qué me refiero:


 def test_foo
   ...
   foo = Foo.find_by_nicename('foo_bar')
   assert_not_nil foo
   # utiliza foo tranquilamente
   ...
 end
 

O también:


 def test_foos
   ...
   foos = Foo.find_all_by_parent_id(1)
   assert !foos.empty?
   # utiliza foos tranquilamente
   ...
 end
 

Y es que vais a evitar más de un disgusto comprobando que son válidos los datos que vais a utilizar en el test, un poco en la misma línea de testear tus fixtures.