繁体   English   中英

什么时候以及什么时候不存根/模拟

[英]When and when not to stub/mock a test

我正在齐心协力围绕Rspec努力,以朝着更多的TDD / BDD开发模式迈进。 但是,我还有很长一段路要走,并且在一些基本原则上苦苦挣扎:

就像,什么时候应该使用模拟/存根,什么时候不应该使用?

以这种情况为例:我有一个has_many :blogsSite模型,而Blog模型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

现在,如果我运行该测试,一切都会通过。 但是,它也很慢,因为它为每个测试都创建了一个新站点,两个新Blog和三个新文章! 所以我想知道,这是使用存根的好候选人吗? 让我们开始吧:

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

现在,所有测试仍然通过,而且速度也有所提高。 但是,我的测试时间增加了一倍,而整个练习却毫无意义,因为我不再测试代码,而只是测试了。

现在,或者我完全错过了模拟/存根的要点,或者我从根本上解决了这个问题,但是我希望有人能够做到:

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

但是,我的测试时间增加了一倍,而整个练习却毫无意义,因为我不再测试代码,而只是测试了。

这就是这里的关键。 不测试代码的测试没有用。 如果您可以否定地更改您应该测试的代码,并且测试不会失败,则不值得拥有。

根据经验,除非有必要,否则我不喜欢模拟/存根任何内容。 例如,当我编写控制器测试时,我想确保在记录保存失败时发生适当的动作,我发现将对象的save方法存根以返回false更容易,而不是仅仅精心设计参数因此,为了确保模型无法保存。

另一个示例是一个名为admin?的助手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) }

这就是为什么我很少使用存根/模拟的原因(仅当我要使用外部Web服务时才使用)。 节省的时间不值得增加额外的复杂性。

有更好的方法可以加快测试时间,Nick Gauthier很好地介绍了很多方法,请参见视频幻灯片

另外,我认为一个不错的选择是尝试使用内存中的sqlite数据库进行测试。 这样就不必在磁盘上存放所有东西,从而可以大大减少数据库时间。 不过,我自己还没有尝试过(我主要使用MongoDB,它具有相同的好处),因此请谨慎操作。 这是一篇有关它的近期博客文章。

我不太同意其他人。 真正的问题(如我所见)是,您正在使用相同的测试(发现行为和创建)测试多个有趣的行为。 有关导致此问题的原因的详细信息,请参见此讨论: http : //www.infoq.com/presentations/integration-tests-scam 对于此答案的其余部分,我假设您要测试创建的内容就是您要测试的内容。

隔离测试似乎很笨拙,但这通常是因为它们有设计课程来教您。 以下是我可以从中看到的一些基本内容(尽管没有看到生产代码,但是我做不到太多的事情)。

首先,要查询设计,让Site向博客添加文章是否有意义? Blog上的类方法Blog.with_one_articleBlog.with_one_article 然后,这意味着您需要测试的是该类方法已被调用两次(如果[据我目前所知],则每个Site都有一个“主” Blog和“辅助” Blog ,并且已经建立了关联(我还没有找到在Rails中执行此操作的好方法,我通常不对其进行测试)。

此外,您在调用Site.create时是否覆盖ActiveRecord的create方法? 如果是这样,我建议在Site上创建一个新的类方法,命名为其他名称(可能是Site.with_default_blogs ?)。 这只是我的一个普遍习惯,过多的东西通常会在以后的项目中引起问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM