2010-09-13 67 views
8

为了向更多TDD/BDD开发模式迈进,我正在齐心协力围绕Rspec进行研究。然而,我还有很长一段路要走,并且在一些基本面上挣扎:何时以及何时不存根/模拟测试

就像我应该在什么时候使用模拟/存根,什么时候不应该?举例来说,这种情况下:我有一个Site模型has_many :blogsBlog模型has_many :articles。在我的Site模型中,我有一个回调过滤器,为每个新网站创建一组默认的博客和文章。我想测试该代码,所以这里有:

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 

现在,如果我运行该测试,一切都会通过。然而,它也很慢,因为它会创建一个新网站,两个新博客和三个新文章 - 每一个测试!所以我想知道,这是使用存根的好候选者吗?让我们一起去吧:

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 

现在所有的测试仍然通过,事情也有点快。但是,我的测试时间增加了一倍,整个练习让我觉得完全毫无意义,因为我不再测试我的代码,我只是测试我的测试。现在

,或者我已经完全错过了嘲笑/存根的点,或者我接近它根本上就是错误的,但我希望有人也许能够之一:

  • 提高我上面的测试所以它以实际测试我的代码的方式使用存根或模拟,而不是我的测试。
  • 或者,告诉我是否应该在这里使用存根 - 或者实际上这是完全不必要的,我应该将这些模型写入测试数据库。

回答

2

但是,我已经翻了一番我的测试长度和整个演习只是令我毫无意义,因为我不再是测试我的代码,我只是在测试我的测试。

这是关键在这里。不测试代码的测试没有用处。如果你可以负面地改变你的测试应该测试的代码,并且测试不会失败,那么他们不值得拥有。作为一个经验法则,除非必须,否则我不喜欢模拟/残缺任何东西。例如,当我正在编写一个控制器测试时,我想确保在记录无法保存时发生相应的操作,我发现将对象的save方法存根返回false会比较简单,而不是仔细制作参数所以为了确保模型无法保存。

另一个例子是一个名为admin?的助手,根据当前登录的用户是否是管理员而返回true或false。我并不想通过伪造用户登录,所以我这样做:

# 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 

作为一个中间地带,你可能想看看Shoulda。通过这种方式,您可以确保您的模型具有定义的关联,并且相信Rails经过充分测试,足以使该关联“无需工作”,而无需创建关联模型并对其进行计数。

我有一个名为Member的模型,基本上我的应用程序中的所有内容都与之相关。它定义了10个关联。我可以测试每个这些关联的,或者我可能只是这样做:

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

谢谢,这是一个有用的回应。:) – aaronrussell 2010-09-14 12:41:36

1

这就是为什么我使用存根/嘲笑很少(实际上只有当我将要触及的外部Web服务)。节省的时间不值得增加复杂性。

有更好的方法来加快你的测试时间,Nick Gauthier给出了一个很好的谈话,涵盖了他们一堆 - 见videoslides

此外,我认为一个好的选择是尝试一个内存中的sqlite数据库为您的测试运行。这应该可以减少数据库时间,因为不必为所有内容都打满磁盘。我自己并没有尝试过,但(我主要使用MongoDB,它具有相同的优点),所以请谨慎行事。 Here's是一个相当新的博客文章。

+0

谢谢为您的回应和链接。现在观看视频。 – aaronrussell 2010-09-14 12:42:30

1

我不太确定同意其他人。真正的问题(正如我看到的那样)在于,您正在使用相同的测试(查找行为和创建)测试多个有趣的行为。为什么这是不好的原因,看看这个谈话:http://www.infoq.com/presentations/integration-tests-scam。我假设这个答案的其余部分是你想测试创建是你想测试的。

孤立症测试通常看起来很笨拙,但这往往是因为他们有设计课程来教你。下面是我可以看到的一些基本的东西(虽然没有看到生产代码,我不能做太多好事)。

对于初学者来说,查询设计,是否有Site添加文章到博客是否有意义? Blog上的类方法怎么样,叫做Blog.with_one_article。这就意味着你所要测试的是这个类的方法被调用了两次(如果[据我现在的理解],你对每个Site都有一个“主”和“次”Blog,并且关联建立了(我还没有找到一个很好的方法来做到这一点在轨道上,但我通常不会测试它)

此外,你是否重写ActiveRecord的创建方法,当你打电话Site.create?如果是这样,我会建议做一个新的类方法在Site上命名为别的东西(Site.with_default_blogs可能?)。这只是我的一个普通习惯,覆盖东西通常会导致后续项目中出现问题。

相关问题