¿Una verdad incómoda?
21 Jan 08Shoulda es un plugin para hacer BDD en Rails. Se trata de una alternativa sencilla a RSpec que, a diferencia de este, no proporciona un framework completo sino una serie de helpers, macros y assertions sobre la librería estándar de testing de Ruby. Así, permite escribir tests especificar comportamientos, por ejemplo, de la siguiente manera:
class PostTest < Test::Unit::TestCase
load_all_fixtures
should_belong_to :user
should_have_many :tags, :through => :taggings
should_require_unique_attributes :title
should_require_attributes :body, :message => /wtf/
should_require_attributes :title
should_only_allow_numeric_values_for :user_id
end
He robado el ejemplo de la misma página principal del plugin. El caso es que, para satisfacer el test, podríamos escribir una clase parecida a esta:
class Post < ActiveRecord::Base
belongs_to :user
has_many :tags, :through => :taggings
validates_uniqueness_of :title
validates_presence_of :body, :message => "wtf"
validates_presence_of :title
validates_numericability_of :user_id
end
No lo he llegado a testear, pero tiene pinta de que más o menos esa sería la implementación. La cuestión es que no dejo de tener la sensación de que hay algo incorrecto en esta manera de testear. El test se corresponde línea a línea con la implementación. Es lo mismo, pero dicho con otro lenguaje. Sería perfectamente posible generar la clase a partir del test. Con lo que llego a la conclusión de que lo único que hacemos siguiendo este enfoque es escribir dos veces lo mismo, duplicar la información y, por tanto, hacer menos mantenible nuestra aplicación.
Me inquieta especialmente pensar que Shoulda no es más que una refactorización sobre la manera estándar de testear en Rails. Por ejemplo:
class PostTest < Test::Unit::TestCase
...
# Esto:
should_require_attributes :title
# Es lo mismo que esto:
def test_should_require_title
post = Post.new
assert !post.valid?
assert post.errors.on(:title)
end
De hecho, con un poquito de metaprogramación podríamos facilmente escribir nuestra propia versión del should_require_attributes:
def should_require_attributes(attribute)
define_method "test_should_require_#{attribute}" do
klass = self.name.gsub(/Test$/, '').constantize
object = klass.new
assert !object.valid?
assert object.errors.on(attribute)
end
end
Y he aquí la angustiosa conclusión a la que llego, que me hace despertar con taquicardias por los noches: Si los tests declarativos de Shoulda me parecen incorrectos, y los tests declarativos de Shoulda son solo una refactorización y, por tanto, equivalentes funcionalmente a la manera normal de testear en Rails:
TODO LO DECLARATIVO DE MIS MODELOS NO TIENE SENTIDO SER TESTEADO A NIVEL DE UNIDAD
Dicho queda, en mayúsculas y negrita. A ver si alguien encuentra el error en mi razonamiento, el detalle que se me escapa, desmonta mi teoría, y por fin me devuelve el sosiego, la paz interior y el flow.