1

我正在学习 rspec 教程(peepcode 教程)。我生成了一些脚手架,我希望有人可以帮助解释如何重写描述以使新手更清楚地阅读。

describe "POST create" do

    describe "with valid params" do
      it "assigns a newly created weather as @weather" do
        Weather.stub(:new).with({'these' => 'params'}) { mock_weather(:save => true) }
        post :create, :weather => {'these' => 'params'}
        assigns(:weather).should be(mock_weather)
      end

end

这行代码就是我想要理解的就是这个

Weather.stub(:new).with({'these' => 'params'}) { mock_weather(:save => true) }

我从未见过将方法放在大括号内。这实际上意味着什么?

{ mock_weather(:save => true) }
4

2 回答 2

6

该声明的意思是:

使用参数存根new类上的方法并返回表达式的值Weather'these'=>'params'mock_weather(:save => true)

一个类似的、也许更清晰的写法是:

Weather.stub(:new).with({'these'=>'params'}).and_return(mock_weather(:save => true))

语法{< some code>}创建一个代码块,该代码块在调用存根时执行。

两种形式的返回值.and_return(){}略有不同;在第一种情况下,确定何时定义存根,在第二种情况下,确定何时接收到消息。它们通常可以互换——但有时不能

编辑

我觉得这个答案对模拟具有误导性,值得回应:

至于您关于“如何使其更清晰”的第一个问题,我们可以从不使用模拟开始。当您不能依赖辅助对象的可预测行为时,模拟对象很有用,辅助对象不重要但必须存在于您的测试用例中。使用模拟对象的典型示例是数据库查询、网络使用、文件 i/o。您不希望您的测试因为您的计算机丢失网络连接或数据库不可用而失败。

诚然,模拟可以消除对外部资源的依赖,但这不是它们的唯一目的。模拟的真正价值在于它们允许您为尚不存在的代码编写测试。例如,在 Rails 中,您可能决定先编写视图规范。

describe "posts/show.html.erb" do
  it "displays the author name" do
    assign(:post,mock('post',:author=>"Mark Twain"))
    render
    rendered.should contain("written by Mark Twain")
  end
end

该规范不要求存在数据库、控制器或模型。它所做的只是断言视图需要渲染一个字符串并验证它是否被渲染——这就是 Rails 视图应该关心的所有内容。唯一的依赖关系是模板文件和实例变量的存在@post,它们由assign语句处理。它甚至不关心是什么@post,只关心它对:author.

您从 rails g 脚手架获得的生成代码不是最佳的。生成的代码以使所有测试通过的方式生成,为此它使用模拟对象。我不知道他们为什么这样做,我认为更好的默认设置是测试失败,因此您实际上需要做一些事情才能使它们通过。

脚手架的整个想法是通过生成适用于通常用例的代码来节省时间。您不希望生成的测试也能正常工作吗?

当然,通过使用脚手架,您正在围绕 BDD/TDD“测试优先”范式进行最终运行,但可能您已经接受了相关的权衡,或者您一开始就不会使用脚手架。

至于“为什么使用模拟对象”,它们允许控制器规范与模型和数据库分离。所以是的,一旦你知道了推理,它就是“最佳的”。

使用自动生成的模拟文件,您根本不需要做任何事情,测试将永远通过。这是一个坏主意,也是不好的做法。

只要您不破坏主题代码,它们就会通过。因此,它们在回归测试中具有价值,可确保您不会引入新代码或以导致代码不再符合规范的方式进行重构。

由于必须在模型文件中编写验证规则,并且您没有使用模拟对象,因此您可以确定正在进行实际验证。

这种耦合在 Rails 控制器规范中实际上是不可取的。控制器应该尽可能少地了解模型,因此控制器规范只需要定义验证通过(或失败)时会发生什么——而脚手架提供的模拟正是这样做的。如果您需要测试模型实例对于给定的一组参数是否有效,请在模型规范中执行此操作。

于 2011-02-04T17:20:34.053 回答
1

至于您关于“如何使其更清晰”的第一个问题,我们可以从不使用模拟开始。

当您不能依赖辅助对象的可预测行为时,模拟对象很有用,辅助对象不重要但必须存在于您的测试用例中。使用模拟对象的典型示例是数据库查询、网络使用、文件 i/o。您不希望您的测试因为您的计算机丢失网络连接或数据库不可用而失败。

您从中获得的生成代码rails g scaffold不是最佳的。生成的代码以使所有测试通过的方式生成,为此它使用模拟对象。我不知道他们为什么这样做,我认为更好的默认设置是测试失败,因此您实际上需要做一些事情来让它们通过。

我会删除生成的模拟并执行以下操作:

#spec/controllers/weather_controller_spec.rb
describe "POST create" do
  describe "with valid params" do
    it "assigns a newly created weather as @weather" do
      post :create, :weather => {'location' => 'ORD', 'temp'=>'35', 'sample_time'=>'2011-02-04T20:00-0500'}
      assigns(:weather).should be_valid
    end

    it "should redirect you to the weather show page" do
      post :create, :weather => {'location' => 'ORD', 'temp'=>'35', 'sample_time'=>'2011-02-04T20:00-0500'}
      response.should redirect_to(weather_path(assigns[:weather]))
    end
  end

  describe "without valid params" do
    it "should notify that a location is required" do
      post :create, :weather => {'temp'=>'35', 'sample_time'=>'2011-02-04T20:00-0500'}
      flash[:notice].should == 'Location is required.'
      assigns(:weather).should_not be_valid
    end

    it "should notify that a temperature is required" do
      post :create, :weather => {'location' => 'ORD', 'sample_time'=>'2011-02-04T20:00-0500'}
      flash[:notice].should == 'A temperature is required.'
      assigns(:weather).should_not be_valid
    end

    it "should notify that a sample time is required" do
      post :create, :weather => {'location' => 'ORD', 'temp'=>'35'}
      flash[:notice].should == 'A sample time is required.'
      assigns(:weather).should_not be_valid
    end
  end
end

请注意,我们没有使用模拟对象,因此代码简化为使用一些参数进行 POST 调用并验证该对象是否有效。由于必须在模型文件中编写验证规则,并且您没有使用模拟对象,因此您可以确定正在进行实际验证。

使用自动生成的模拟文件,您根本不需要做任何事情,测试将永远通过。这是一个坏主意,也是不好的做法。

另请注意,您应该编写更多测试来处理无效或缺少参数的情况。再一次,通过这样做,assigns(:weather).should_not be_valid您正在验证您的验证是否正在完成他们的工作。

每次调用时都编写参数字典post :create是重复的、脆弱的和丑陋的。你应该学习如何使用夹具。例如,与工厂女孩

#spec/factories.rb
Factory.define :weather_valid do |f|
  f.location "ORD"
  f.temp "35"
  f.sample_time "2011-02-04T20:00-0500"
end


#spec/controllers/weather_controller_spec.rb
describe "POST create" do
  describe "with valid params" do
    it "assigns a newly created weather as @weather" do
      post :create, :weather => Factory.build(:weather_valid).attributes
      assigns(:weather).should be_valid
    end

    it "should redirect you to the weather show page" do
      post :create, :weather => Factory.build(:weather_valid).attributes
      response.should redirect_to(weather_path(assigns[:weather]))
    end
  end
...

这为您提供了可重用且更具可读性的代码。

于 2011-02-04T18:05:08.987 回答