【问题标题】:RSpec Scaffold Controller, understanding the defaults being givenRSpec Scaffold Controller,了解给出的默认值
【发布时间】:2011-06-21 01:39:46
【问题描述】:

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

【问题讨论】:

    标签: rspec controllers scaffold


    【解决方案1】:

    这句话的意思是:

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

    类似且可能更清晰的写法是:

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

    语法{some code>} 创建一个code block,在调用存根时执行。

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

    编辑

    我觉得this answer 对模拟具有误导性,应该得到回应:

    关于你的第一个问题是“如何 让它更清楚一点”,我们可以 从不使用模拟开始。一个模拟 当你不能时,对象很有用 取决于一个可预测的行为 次要对象,一个对象是 不重要但必须出现在 你的测试用例。的典型例子 模拟对象的使用是数据库 查询、网络使用情况、文件 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 控制器规范中是不可取的。控制器应该尽可能少地了解模型,因此控制器规范只需要定义验证通过(或失败)时会发生什么——而脚手架提供的模拟正是这样做的。如果您需要测试模型实例对于给定的一组参数是否有效,请在模型规范中进行。

    【讨论】:

    • 感谢更清晰的版本。我希望他们只是将其用作默认值,而不是其他方式。代码块 URL 已添加书签,是一个很好的解释和示例。
    【解决方案2】:

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

    当您不能依赖次要对象的可预测行为时,模拟对象很有用,次要对象并不重要,但必须存在于您的测试用例中。使用模拟对象的典型示例是数据库查询、网络使用、文件 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
    ...
    

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

    【讨论】:

    • 感谢您的解释。您的示例使事情变得非常清楚,感谢您花时间解释这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    • 2020-07-20
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 2019-07-24
    相关资源
    最近更新 更多