2010-09-13 13 views
8

Estoy haciendo un esfuerzo conjunto para rodear Rspec con el fin de avanzar hacia un patrón de desarrollo TDD/BDD. Sin embargo, estoy muy lejos y estoy luchando con algunos de los fundamentos:Cuándo y cuándo no resbalar/simular una prueba

Me gusta, ¿cuándo exactamente debería estar usando mocks/stubs y cuándo no?

Tomemos por ejemplo este escenario: Tengo un modelo Site que has_many :blogs y el modelo has_many :articlesBlog. En mi modelo Site tengo un filtro de devolución de llamada que crea un conjunto predeterminado de blogs y artículos para cada sitio nuevo. Quiero probar que el código, así que aquí va:

describe Site, "when created" do 

    include SiteSpecHelper 

    before(:each) do 
    @site = Site.create valid_site_attributes 
    end 

    it "should have 2 blogs" do 
    @site.should have(2).blogs 
    end 

    it "should have 1 main blog article" do 
    @site.blogs.find_by_slug("main").should have(1).articles 
    end 

    it "should have 2 secondary blog articles" do 
    @site.blogs.find_by_slug("secondary").should have(2).articles 
    end 

end 

Ahora, si me quedo esa prueba, todo pasa. Sin embargo, también es bastante lento ya que está creando un nuevo sitio, dos nuevos blogs y tres nuevos artículos, ¡para cada prueba individual! Entonces me pregunto, ¿es este un buen candidato para usar talones? Vamos a darle una oportunidad:

describe Site, "when created" do 

    include SiteSpecHelper 

    before(:each) do 
    site = Site.new 
    @blog = Blog.new 
    @article = Article.new 
    Site.stub!(:create).and_return(site) 
    Blog.stub!(:create).and_return(@blog) 
    Article.stub!(:create).and_return(@article) 
    @site = Site.create valid_site_attributes 
    end 

    it "should have 2 blogs" do 
    @site.stub!(:blogs).and_return([@blog, @blog]) 
    @site.should have(2).blogs 
    end 

    it "should have 1 main blog article" do 
    @blog.stub!(:articles).and_return([@article]) 
    @site.stub_chain(:blogs, :find_by_slug).with("main").and_return(@blog) 
    @site.blogs.find_by_slug("main").should have(1).articles 
    end 

    it "should have 2 secondary blog articles" do 
    @blog.stub!(:articles).and_return([@article, @article]) 
    @site.stub_chain(:blogs, :find_by_slug).with("secondary").and_return(@blog) 
    @site.blogs.find_by_slug("secondary").should have(2).articles 
    end 

end 

Ahora todas las pruebas aún pasan, y las cosas son un poco más rápidas también. Pero he duplicado la duración de mis pruebas y todo el ejercicio me parece completamente absurdo, porque ya no estoy probando mi código, solo estoy probando mis pruebas.

Ahora, o me he perdido por completo el punto de burla de los talones/o estoy acercarse a ella fundamentalmente equivocado, pero estoy esperando que alguien podría ser capaz de, ya sea:

  • Mejorar Me pruebas anteriores entonces usa stubs o burlas de una forma que realmente prueba mi código, en lugar de mis pruebas.
  • O, dígame si incluso debería usar stubs aquí - o si de hecho esto es completamente innecesario y debería escribir estos modelos en la base de datos de prueba.

Respuesta

2

Pero, He doblado la longitud de mis pruebas y todo el ejercicio simplemente me parece sin sentido por completo, porque ya no estoy probando mi código, sólo estoy probando mis pruebas.

Esta es la clave aquí. Las pruebas que no prueban tu código no son útiles. Si puede cambiar negativamente el código que se supone que deben probar sus pruebas, y las pruebas no fallan, no vale la pena tenerlas.

Como regla general, no me gusta burlarse de nada a menos que sea necesario. Por ejemplo, cuando estoy escribiendo una prueba de controlador, y quiero asegurarme de que se produzca la acción adecuada cuando un registro no se guarda, me resulta más fácil anular el método save del objeto para devolver el resultado falso, en lugar de crear parámetros cuidadosamente solo para asegurarse de que un modelo no pueda guardarse

Otro ejemplo es para un ayudante llamado admin? que simplemente devuelve verdadero o falso en función de si el usuario que ha iniciado sesión actualmente es o no administrador. Yo no quiero pasar por falsificar un inicio de sesión de usuario, así que hice esto:

# helper 
def admin? 
    unless current_user.nil? 
    return current_user.is_admin? 
    else 
    return false 
    end 
end 

# spec 
describe "#admin?" do 
    it "should return false if no user is logged in" do 
    stubs(:current_user).returns(nil) 
    admin?.should be_false 
    end 

    it "should return false if the current user is not an admin" do 
    stubs(:current_user).returns(mock(:is_admin? => false)) 
    admin?.should be_false 
    end 

    it "should return true if the current user is an admin" do 
    stubs(:current_user).returns(mock(:is_admin? => true)) 
    admin?.should be_true 
    end 
end 

Como término medio, es posible que desee ver en Shoulda. De esta forma, puede asegurarse de que sus modelos tengan una asociación definida como, y confíe en que Rails está suficientemente probado como para que la asociación "funcione" sin tener que crear un modelo asociado y luego contarlo.

Tengo un modelo llamado Member que básicamente está relacionado con todo en mi aplicación. Tiene 10 asociaciones definidas. Me podría poner a prueba cada una de esas asociaciones, o que sólo podía hacer esto:

it { should have_many(:achievements).through(:completed_achievements) } 
it { should have_many(:attendees).dependent(:destroy) } 
it { should have_many(:completed_achievements).dependent(:destroy) } 
it { should have_many(:loots).dependent(:nullify) } 
it { should have_one(:last_loot) } 
it { should have_many(:punishments).dependent(:destroy) } 
it { should have_many(:raids).through(:attendees) } 
it { should belong_to(:rank) } 
it { should belong_to(:user) } 
it { should have_many(:wishlists).dependent(:destroy) } 
+0

Gracias, esa es una respuesta útil. :) – aaronrussell

1

Esto es exactamente por qué uso talones/burla muy raramente (en realidad sólo cuando voy a ser lanzó un servicio web externo). El tiempo ahorrado no vale la complejidad añadida.

Hay mejores formas de acelerar el tiempo de prueba, y Nick Gauthier da una buena charla que cubre un montón de ellas: ver video y slides.

Además, creo que una buena opción es probar una base de datos sqlite en memoria para las ejecuciones de prueba. Esto debería reducir bastante el tiempo de su base de datos al no tener que presionar el disco para todo. Aunque no lo he intentado solo (uso principalmente MongoDB, que tiene el mismo beneficio), así que piénselo con cuidado. Here's una publicación de blog bastante reciente en él.

+0

Gracias por su respuesta y enlaces. Viendo el video ahora. – aaronrussell

1

No estoy tan seguro de estar de acuerdo con los demás. El problema real (como lo veo) aquí, es que estás probando múltiples piezas de comportamiento interesante con las mismas pruebas (el comportamiento de búsqueda y la creación). Por razones sobre por qué esto es malo, vea esta conversación: http://www.infoq.com/presentations/integration-tests-scam. Asumo por el resto de esta respuesta que quieres probar que la creación es lo que quieres probar.

Las pruebas de aislamiento suelen parecer difíciles de manejar, pero a menudo se deben a que tienen lecciones de diseño para enseñarle. A continuación hay algunas cosas básicas que puedo ver a partir de esto (aunque sin ver el código de producción, no puedo hacer demasiado bien).

Para empezar, para consultar el diseño, ¿tiene sentido tener Site agregar artículos a un blog? ¿Qué pasa con un método de clase en Blog llamado algo así como Blog.with_one_article. Esto significa que todo lo que tiene que probar es que ese método de clase ha sido llamado dos veces (si [como lo entiendo ahora], tiene un Blog "primario" y "secundario" para cada Site, y que las asociaciones están configuradas (Todavía no he encontrado una buena manera de hacerlo en rieles, generalmente no lo pruebo).

Además, ¿está anulando el método de creación de ActiveRecord cuando llama al Site.create? De ser así, sugiero que haga un nuevo método de clase en el sitio llamado algo más (Site.with_default_blogs posiblemente?). Este es solo un hábito mío, reemplazar las cosas generalmente causa problemas más adelante en los proyectos.

Cuestiones relacionadas