【问题标题】:RSpec Refactoring HelpRSpec 重构帮助
【发布时间】:2011-03-23 15:47:03
【问题描述】:

在测试或测试驱动开发方面,我是个大菜鸟,所以我在测试我的代码时真的很吃力。造成这种情况的最大原因......

我有一个模型,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. 测试代码与模型代码的比例是否正常,如果是,为什么值得花时间?

谢谢!!

-- 编辑--

明确一点,我最感兴趣的是重构测试代码,而不是定位方法。

【问题讨论】:

    标签: ruby-on-rails ruby testing refactoring rspec


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      很好的观察。我的陪审团同样对 TDD 的理智持怀疑态度。只是在这里检查一下,但是您是否正在运行类似 autotest + growler (Mac OS) 之类的工具来加快您的测试速度?您可能想查看 gem spork 并可能仔细阅读此线程 Why is RSpec so slow under Rails? 以获得有关加速 rspec 执行的许多好的建议。

      这些都不能真正解决您的 cmets 的(rspec 代码)/(程序代码)比率如此之高的问题。我也没有忘记调试我的测试代码的讽刺意味。作为一个快乐和热情的 ruby​​ 和 rails 新手,我对测试驱动的开发持开放态度,但我仍然怀疑它更像是一个隐藏成本而不是好特性 开发范式。

      【讨论】:

      • 我正在运行自动测试,但我没有咆哮者,所以我应该看看!也感谢您的链接!
      【解决方案3】:

      我重构了你的 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
      

      如果您真的很在意规范的行数,有一些方法可以减少规范的行数。自定义匹配器非常棒。

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

      【讨论】:

      • 罗伯特,谢谢你的例子。我同意你的代码更清晰,虽然我有点喜欢阵列供电的案例块:) 但是你对重构规范有什么建议吗?或者这些规格对您来说是否正常。我明白你关于共享代码中测试的价值的观点,我真正的挑战是对于这样的公式,我编写测试所花费的时间大约是编写使它们通过的代码的 10 倍。我正在寻找编写更好测试的方法,这样我就不会花很长时间来编写它们,以至于我根本不想编写它们......
      • 只是“我在这里要学习的内容”的另一点,我觉得考虑我需要的代码比考虑可以验证它的测试要容易得多。因此,关于如何“考虑测试”的建议也会很有用。
      • 对于您正在测试的内容,规范看起来与我预期的一样。充分利用上下文和之前的块来限制重复自己。为了进一步减少自己的重复,我会更多地考虑重构定位方法。将 :full 链接到 :short,这样当你测试 :full 时,你只需要测试与 :short 不同的东西,例如。
      猜你喜欢
      • 1970-01-01
      • 2010-11-19
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 2011-05-17
      • 1970-01-01
      • 1970-01-01
      • 2011-07-18
      相关资源
      最近更新 更多