RSpec 脚手架控制器,了解给出的默认值

发布于 2024-10-15 12:07:30 字数 668 浏览 3 评论 0原文

我正在学习 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) }

I'm working through an rspec tutorial (peepcode tutorial). I generated some scaffold and I was hoping someone can help explain how the describe could be re-written to read a bit clearer for newbie.

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

This line of code is what I'm trying to understand is this one

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

I have never seen a method being put inside braces. What does this actually mean?

{ mock_weather(:save => true) }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

水中月 2024-10-22 12:07:30

该语句的意思是:

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

类似且可能更清晰的编写方式是:

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

语法 {<some code>}< /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 控制器规范中实际上是不受欢迎的。控制器应该尽可能少地了解模型,因此控制器规范只需要定义验证通过(或失败)时会发生什么——而脚手架提供的模拟正是这样做的。如果您需要测试模型实例对于给定的一组参数是否有效,请在模型规范中执行此操作。

That statement means:

Stub the new method on the class Weather with the parameters 'these'=>'params' and return the value of the expression mock_weather(:save => true)

A similar and perhaps clearer way to write this would be:

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

The syntax {<some code>} creates a code block which is executed when the stub is called.

The return values of the two forms .and_return() and {} are slightly different; in the first case it is determined when the stub is defined, in the second case it is determined when the message is received. They are generally interchangeable -- but sometimes not.

EDIT

I feel this answer is misleading about mocks and deserves a response:

As for your first question about "how
to make it a bit clearer", we could
begin by not using mocks. A mock
object is useful when you can not
depend on a predictable behavior of a
secondary object, an object that is
not important but must be present in
your test case. Typical examples of
the usage of mock objects are database
queries, network usage, file i/o. You
don't want your tests to fail because
your computer lost network connection,
or a database is not available.

True, mocks can eliminate dependencies on external resources, but that is not their only purpose. The real value of mocks is that they allow you to write tests for code that doesn’t exist yet. In Rails for example, you might decide to start by writing the view specs first.

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

This spec doesn’t require the existence of a database, a controller, or a model. All it does is assert that the view needs to render a string and verify that it gets rendered --which is all that a Rails view is supposed to be concerned with. The only dependencies are the existence of the template file and the instance variable @post, which is handled by the assign statement. It doesn't even care what @post is, only that it responds to :author.

The generated code that you got from
the rails g scaffold is not optimal.
The generated code was generated in a
way that will make all tests pass, and
for that it uses mock objects. I don't
know why they do that, I think a
better default would be failing tests
so that you actually need to do
something to make them pass.

The whole idea of a scaffold is to save time by generating code that works for the usual use cases. Wouldn’t you want the generated tests to actually work too?

Of course by using scaffolding you are doing an end run around the BDD/TDD “test first” paradigm, but presumably you’ve accepted the tradeoffs involved, or you wouldn’t be using the scaffolds in the first place.

As to "why use mock objects", they allow the controller spec to be decoupled from the model and the database. So yes it is "optimal" once you know the reasoning.

With the auto generated mock files you
don't have to do anything at all and
the tests will continue to pass
forever. That is a bad idea, and bad
practice.

They will only pass as long as you don’t break the subject code. So they have value in regression testing to ensure that you are not introducing new code or refactoring in a way that causes the code to no longer meet the specs.

Since will have to write the
validation rules in your model file,
and you are not using a mock object,
you can be sure that an actual
validation is going on.

This kind of coupling is actually undesirable in a Rails controller spec. The controller should know as little as possible about the model, so the controller specs only need to define what happens when validation passes (or fails) -- and the mock provided by the scaffold does exactly that. If you need to test whether a model instance is valid for a given set of parameters, do that in the model specs.

沐歌 2024-10-22 12:07:30

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

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

rails gscaffold 生成的代码并不是最佳的。生成的代码的生成方式将使所有测试都通过,为此它使用模拟对象。我不知道他们为什么这样做,我认为更好的默认设置是测试失败,这样你实际上需要做一些事情才能让它们通过。

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

#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 时编写参数字典是重复的、脆弱的和丑陋的。你应该研究如何使用灯具。例如,Factory Girl

#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
...

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

As for your first question about "how to make it a bit clearer", we could begin by not using mocks.

A mock object is useful when you can not depend on a predictable behavior of a secondary object, an object that is not important but must be present in your test case. Typical examples of the usage of mock objects are database queries, network usage, file i/o. You don't want your tests to fail because your computer lost network connection, or a database is not available.

The generated code that you got from the rails g scaffold is not optimal. The generated code was generated in a way that will make all tests pass, and for that it uses mock objects. I don't know why they do that, I think a better default would be failing tests so that you actually need to do something to make them pass.

I would delete the generated mocks and do something like the following:

#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

Notice that we are not using mock objects, and so the code is reduced to making the POST call with some parameters and verifying that object is valid. Since will have to write the validation rules in your model file, and you are not using a mock object, you can be sure that an actual validation is going on.

With the auto generated mock files you don't have to do anything at all and the tests will continue to pass forever. That is a bad idea, and bad practice.

Also notice that you should write more tests that exercise the cases where there are invalid or missing parameters. And again, by doing assigns(:weather).should_not be_valid you are verifying that your validations are doing their job.

Writing the dictionary of parameters every time you call post :create is repetitive, fragile and ugly. You should study how to use fixtures. For example, with Factory Girl

#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
...

That gives you reusable and more readable code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文