Ruby on rails RSpec重构帮助

Ruby on rails RSpec重构帮助,ruby-on-rails,ruby,testing,refactoring,rspec,Ruby On Rails,Ruby,Testing,Refactoring,Rspec,当涉及到测试或测试驱动的开发时,我是一个大傻瓜,所以我真的很难测试我的代码。最大的原因是这样的情况 我有一个模型,Photo,它有“地点”、“城市”、“州”和“国家”字段 我创建了一个方法“location”,它可以接受一个参数,并将返回最小形式、简短形式或完整形式的location。以下是模型代码: def location( form=:full ) usa = country.eql?('US') || country.eql?('United States') country_n

当涉及到测试或测试驱动的开发时,我是一个大傻瓜,所以我真的很难测试我的代码。最大的原因是这样的情况

我有一个模型,
Photo
,它有“地点”、“城市”、“州”和“国家”字段

我创建了一个方法“location”,它可以接受一个参数,并将返回最小形式、简短形式或完整形式的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中测试这一点所需的时间要比在控制台中测试所需的时间多得多

因此,我有两个问题:

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

    --编辑--


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

    一个很好的观察结果。我的陪审团对TDD的合理性也持同样的观点。只是在这里检查一下,但是您是否正在运行类似于自动测试+咆哮者(Mac OS)的程序来加快测试速度?您可能想查看gem
    spork
    ,或许可以仔细研究这个线程,以获得许多关于加快rspec执行的好建议


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

    我发现高测试/代码比率是完全正常的,甚至是需要的。一般来说,测试代码应该比普通代码更容易“分解”。它们被称为示例是有原因的:测试应该向人们展示如何使用您的代码,最好的方法是对每个示例都非常简单

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


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

    我重构了你们的
    位置
    方法,应该注意的是,我能够这样做并确保我没有破坏任何东西的唯一原因是因为。。。等等。。。你有测试

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

      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
    
    如果您真的关心规格的行数,那么有一些方法可以减少规格的行数。自定义匹配器是一个很好的匹配器


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

    我正在运行自动测试,但我没有咆哮者,因此我应该注意这一点!也谢谢你的链接!罗伯特,谢谢你的例子。我同意你的代码更清晰,尽管我有点喜欢数组驱动的case块:)但是你对重构规范有什么建议吗?或者这些规格对你来说正常吗。我明白你关于测试在共享代码中的价值的观点,我真正的挑战是对于这样一个公式,我花了大约10倍的时间来编写测试,而不是编写让它们通过的代码。我正在寻找编写更好的测试的方法,这样我就不会花太长时间来编写它们,以至于我根本不想编写它们……这只是“我在这里试图学习的东西”的另一点,我觉得思考我需要的代码比思考验证它的测试要容易得多。因此,关于如何“思考测试”的建议也会很有用。对于您正在测试的内容,规范看起来和我预期的一样。充分利用上下文和前块来限制重复。为了进一步减少重复,我将更多地研究重构location方法。将其设置为:full-chains到:short,这样当您测试:full时,您只需测试与:short不同的内容。