RSpec 重构帮助

发布于 2024-10-25 23:10:44 字数 4492 浏览 3 评论 0原文

在测试或测试驱动开发方面,我是一个大菜鸟,所以我真的很难测试我的代码。最大的原因是这样的情况......

我有一个模型,Photo,其中包含“地点”、“城市”、“州”和“国家”字段。

我创建了一种方法“location”,它可以接受一个参数,并将返回最小形式、简短形式或完整形式位置。这是模型代码:

def location( form=:full )
  usa = country.eql?('US') || country.eql?('United States')
  country_name = Carmen::country_name( self.country )

  if self.state.present?
    state_name = Carmen::state_name( self.state )
  end

  case [form, usa]
  when [:min, true] then ( self.city.blank? ? self.place : self.city )
  when [:min, false] then ( self.city.blank? ? self.place : self.city )
  when [:short, true] then [ ( self.city.blank? ? self.place : self.city ), state_name ].join(', ')
  when [:short, false] then [ ( self.city.blank? ? self.place : self.city ), country_name ].join(', ')
  when [:full, true] then [ self.place, self.city, state_name ].delete_if{|a| a.blank? }.join(', ')
  when [:full, false] then [ self.place, self.city, country_name ].delete_if{|a| a.blank? }.join(', ')
  else raise "Invalid location format"
  end
end

正如您所看到的,它相当干净简洁。现在,这是我的规范:

describe "location" do

  before do
    @us = Photo.new(:place => "Main St.", :city => "Barville", :state => "TX", :country => "US")
    @uk = Photo.new(:place => "High St.", :city => "Barchester", :country => "GB")
    @it = Photo.new( :place => "il Commune", :city => "Baria", :state => "AR", :country => "IT" )
  end

  context "minimum form" do
    it "should return city or place for US Photos" do
      @us.location(:min).should == "Barville"
      @us.city = ""
      @us.location(:min).should == "Main St."
    end

    it "should return city or place for Internationals without states" do
      @uk.location(:min).should == "Barchester"
      @uk.city = ""
      @uk.location(:min).should == "High St."
    end

    it "should return city or place for Internationals with states" do
      @it.location(:min).should == "Baria"
      @it.city = ""
      @it.location(:min).should == "il Commune"
    end
  end

  context "short form" do
    it "should return city,state or place,state for US photos" do
      @us.location(:short).should == "Barville, Texas"
      @us.city = ""
      @us.location(:short).should == "Main St., Texas"
    end

    it "should return city,country or place,country for Internationals without states" do
      @uk.location(:short).should == "Barchester, United Kingdom"
      @uk.city = ""
      @uk.location(:short).should == "High St., United Kingdom"
    end

    it "should return city,country or place,country for Internationals with states" do
      @it.location(:short).should == "Baria, Italy"
      @it.city = ""
      @it.location(:short).should == "il Commune, Italy"
    end
  end

  context "full form" do
    context "US Photos" do
      it "should return place, city, state" do
        @us.location(:full).should == "Main St., Barville, Texas"
      end

      it "should return place, state if city is blank" do
        @us.city = ""
        @us.location(:full).should == "Main St., Texas"
      end

      it "should return city, state if place is blank" do
        @us.place = ""
        @us.location(:full).should == "Barville, Texas"
      end
    end

    context "Internationals without states" do
      it "should return place, city, state" do
        @uk.location(:full).should == "High St., Barchester, United Kingdom"
      end

      it "should return place, state if city is blank" do
        @uk.city = ""
        @uk.location(:full).should == "High St., United Kingdom"
      end

      it "should return city, state if place is blank" do
        @uk.place = ""
        @uk.location(:full).should == "Barchester, United Kingdom"
      end
    end
  end

  context "Internationals with states" do
    it "should return place, city, state" do
      @it.location(:full).should == "il Commune, Baria, Italy"
    end

    it "should return place, state if city is blank" do
      @it.city = ""
      @it.location(:full).should == "il Commune, Italy"
    end

    it "should return city, state if place is blank" do
      @it.place = ""
      @it.location(:full).should == "Baria, Italy"
    end
  end
end

所以,98 行测试代码来测试 17 行模型代码。这对我来说似乎很疯狂。另外,坦率地说,在 RSpec 中测试这一点所需的时间比在控制台中测试所需的时间多得多。

所以,我有两个问题:

  1. 当然有更好的方法来做到这一点。有人可以建议重构吗?
  2. 测试代码与模型代码的比例是否正常?如果是,为什么值得花时间?

谢谢!!

-- 编辑 --

需要明确的是,我最感兴趣的是测试代码的重构,而不是位置方法。

I'm a big noob when it comes to testing or test-driven development, so I'm really struggling with testing my code. The biggest reason why is situations like this...

I have a model, Photo, which has the fields "place", "city", "state", and "country".

I created a method, "location", which can accept one argument, and will return either the minimum form, short form, or full form location. Here's the model code:

def location( form=:full )
  usa = country.eql?('US') || country.eql?('United States')
  country_name = Carmen::country_name( self.country )

  if self.state.present?
    state_name = Carmen::state_name( self.state )
  end

  case [form, usa]
  when [:min, true] then ( self.city.blank? ? self.place : self.city )
  when [:min, false] then ( self.city.blank? ? self.place : self.city )
  when [:short, true] then [ ( self.city.blank? ? self.place : self.city ), state_name ].join(', ')
  when [:short, false] then [ ( self.city.blank? ? self.place : self.city ), country_name ].join(', ')
  when [:full, true] then [ self.place, self.city, state_name ].delete_if{|a| a.blank? }.join(', ')
  when [:full, false] then [ self.place, self.city, country_name ].delete_if{|a| a.blank? }.join(', ')
  else raise "Invalid location format"
  end
end

As you can see that's fairly clean and concise. Now, here's my spec:

describe "location" do

  before do
    @us = Photo.new(:place => "Main St.", :city => "Barville", :state => "TX", :country => "US")
    @uk = Photo.new(:place => "High St.", :city => "Barchester", :country => "GB")
    @it = Photo.new( :place => "il Commune", :city => "Baria", :state => "AR", :country => "IT" )
  end

  context "minimum form" do
    it "should return city or place for US Photos" do
      @us.location(:min).should == "Barville"
      @us.city = ""
      @us.location(:min).should == "Main St."
    end

    it "should return city or place for Internationals without states" do
      @uk.location(:min).should == "Barchester"
      @uk.city = ""
      @uk.location(:min).should == "High St."
    end

    it "should return city or place for Internationals with states" do
      @it.location(:min).should == "Baria"
      @it.city = ""
      @it.location(:min).should == "il Commune"
    end
  end

  context "short form" do
    it "should return city,state or place,state for US photos" do
      @us.location(:short).should == "Barville, Texas"
      @us.city = ""
      @us.location(:short).should == "Main St., Texas"
    end

    it "should return city,country or place,country for Internationals without states" do
      @uk.location(:short).should == "Barchester, United Kingdom"
      @uk.city = ""
      @uk.location(:short).should == "High St., United Kingdom"
    end

    it "should return city,country or place,country for Internationals with states" do
      @it.location(:short).should == "Baria, Italy"
      @it.city = ""
      @it.location(:short).should == "il Commune, Italy"
    end
  end

  context "full form" do
    context "US Photos" do
      it "should return place, city, state" do
        @us.location(:full).should == "Main St., Barville, Texas"
      end

      it "should return place, state if city is blank" do
        @us.city = ""
        @us.location(:full).should == "Main St., Texas"
      end

      it "should return city, state if place is blank" do
        @us.place = ""
        @us.location(:full).should == "Barville, Texas"
      end
    end

    context "Internationals without states" do
      it "should return place, city, state" do
        @uk.location(:full).should == "High St., Barchester, United Kingdom"
      end

      it "should return place, state if city is blank" do
        @uk.city = ""
        @uk.location(:full).should == "High St., United Kingdom"
      end

      it "should return city, state if place is blank" do
        @uk.place = ""
        @uk.location(:full).should == "Barchester, United Kingdom"
      end
    end
  end

  context "Internationals with states" do
    it "should return place, city, state" do
      @it.location(:full).should == "il Commune, Baria, Italy"
    end

    it "should return place, state if city is blank" do
      @it.city = ""
      @it.location(:full).should == "il Commune, Italy"
    end

    it "should return city, state if place is blank" do
      @it.place = ""
      @it.location(:full).should == "Baria, Italy"
    end
  end
end

So, 98 lines of test code to test 17 lines of model code. That just seems insane to me. Plus the time it takes to test this out in RSpec is frankly a lot more than the time it takes to test it out in the console.

So, I have two questions:

  1. Surely there's a better way to do this. Can anyone suggest a refactoring?
  2. Is this ratio of test code to model code normal, and if so, why is it worth the time?

Thanks!!

-- EDIT --

Just to be clear, I'm mostly interested in a refactoring of the test code, not the location method.

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

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

发布评论

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

评论(3

世界如花海般美丽 2024-11-01 23:10:44

我发现高测试/代码比率是完全正常的,甚至是期望的。一般来说,测试代码应该比正常代码更“清晰”。它们被称为示例是有原因的:测试应该向人们展示如何使用您的代码,而做到这一点的最佳方法就是每个示例都非常简单。

编写相对冗长的测试还可以帮助您避免调试它们。测试应该简单易读,而不是紧凑和高效。当您不必首先弄清楚任何复杂的循环、迭代、递归或元编程时,就更​​容易理解每​​个测试正在做什么。

也就是说,您应该始终警惕寻找可以放置在 before(:each) 块中的代码段。看起来你在这方面都做得很好。

I've found that a high test/code ratio is perfectly normal and even desired. Generally test code should be more "unravelled" than normal code. They're called examples for a reason: tests should show people how to use your code, and the best way to do that is to be stupidly simple with each example.

Writing relatively long-winded tests also helps you keep from having to debug them. Tests should be straightforward and readable rather than compact and efficient. It's much easier to understand what each test is doing when you don't have to first figure out any complicated loops, iterations, recursion or metaprogramming.

That said, you should always be vigilant about looking for segments of code that can be placed in a before(:each) block. It looks like you're all good on that front.

絕版丫頭 2024-11-01 23:10:44

一个很好的观察。我的评审团同样对 TDD 的合理性持怀疑态度。只是检查这里,但是您是否正在运行诸如 autotest + growler (Mac OS) 之类的东西来加快测试速度?您可能想研究一下 gem spork 并可能仔细考虑这个线程 为什么 RSpec 在 Rails 下如此慢? 有关加速 rspec 执行的许多好建议。

这些都没有真正解决您对(rspec 代码)/(程序代码)比率如此高的评论。我也没有忘记必须调试我的测试代码的讽刺意味。作为一个快乐而热情的 Ruby 和 Rails 新手,我对测试驱动开发保持开放的态度,但我仍然怀疑它更多的是隐藏成本而不是好的功能 发展范式。

A great observation. My jury is similarly out on the sanity of TDD. Just checking here, but are you running something like autotest + growler (Mac OS) to speed your testing? You may want to look into the gem spork and perhaps mull through this thread Why is RSpec so slow under Rails? for lots of good advice on speeding rspec execution.

None of this really addresses your comments on the (rspec code)/(program code) ratio being so high. The irony of having to debug my testing code is not lost on me either. As a happy and enthusiastic newcomer to ruby and rails, I am keeping an open mind on testing driven development, but I remain suspicious that it is more a hidden cost rather than good feature of the development paradigm.

甜是你 2024-11-01 23:10:44

我重构了你的 location 方法,应该注意的是,我可以这样做并确保我没有破坏任何东西的唯一原因是......等等......你有测试!

这不一定更好,或者性能更高,但在我看来它更具可读性。我相信这还可以进一步改进。

  def location( form=:full )
    if respond_to? "location_#{form}"
      send("location_#{form}")
    else
      raise ArgumentError, "Invalid location format"
    end
  end

  protected

  def location_min
    city.blank? ? place : city
  end

  def location_short
    [(city.blank? ? place : city), (usa? ? state_name : country_name)].join(', ')
  end

  def location_full
    [place, city, (usa? ? state_name : country_name)].delete_if { |v| v.blank? }.join(', ')
  end

  def usa?
    country.eql?('US') || country.eql?('United States')
  end

  def state_name
    Carmen::state_name(self.state) if self.state.present?
  end

  def country_name
    Carmen::country_name(self.country)
  end

如果您确实关心规格的行数,可以通过一些方法来减少该行数。定制匹配器是一个很棒的匹配器。

关键是,测试可以防止其他人,甚至您自己,破坏您编写的代码。当然,您可以只在控制台中进行测试,但这很快就会变得复杂且乏味,并且更容易出现人为错误。

I refactored your location method, and it should be noted that the only reason I could do this and be sure I wasn't breaking anything was because... wait for it... you had tests!

This isn't necessarily better, or more performant, but in my opinion it's much more readable. And I'm sure that could be further improved upon.

  def location( form=:full )
    if respond_to? "location_#{form}"
      send("location_#{form}")
    else
      raise ArgumentError, "Invalid location format"
    end
  end

  protected

  def location_min
    city.blank? ? place : city
  end

  def location_short
    [(city.blank? ? place : city), (usa? ? state_name : country_name)].join(', ')
  end

  def location_full
    [place, city, (usa? ? state_name : country_name)].delete_if { |v| v.blank? }.join(', ')
  end

  def usa?
    country.eql?('US') || country.eql?('United States')
  end

  def state_name
    Carmen::state_name(self.state) if self.state.present?
  end

  def country_name
    Carmen::country_name(self.country)
  end

There are ways to reduce the line count of your specs if you really care about that number. Custom matchers are a great one.

The point is, the tests prevent other people, and even yourself later down the line, from breaking code you wrote. Sure, you could just test things in the console, but that can get complicated very quickly, and tedious, and is way more prone to human error.

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