2012-04-11 58 views
5

我正在創建一個將CSV文件導入到多個表的導入功能。我創建了一個名爲CsvParser的模塊,它解析CSV文件並創建記錄。接收創建操作的模型擴展了CsvParser。他們打電話給CsvParser.create,並傳遞正確的屬性順序和一個可選的lambda,稱爲value_parser。這個lambda將hash中的值轉換爲優先格式。測試lambda

class Mutation < ActiveRecord::Base 
    extend CsvParser 

    def self.import_csv(csv_file) 
    attribute_order = %w[reg_nr receipt_date reference_number book_date is_credit sum balance description] 

    value_parser = lambda do |h| 
     h["is_credit"] = ((h["is_credit"] == 'B') if h["is_credit"].present?) 
     h["sum"] = -1 * h["sum"].to_f unless h["is_credit"] 
     return [h] 
    end 

    CsvParser.create(csv_file, self, attribute_order, value_parser)  
    end 
end 

,我使用支票的拉姆達而不是CsvParser.create方法裏面的原因是因爲拉姆達就像是屬於這種模式業務規則。

我的問題是我應該如何測試這個lambda。我應該在模型還是CsvParser中測試它?我應該測試lambda本身還是self.import方法數組的結果?也許我應該製作另一種代碼結構?

我CsvParser如下所示:

require "csv" 

module CsvParser 

    def self.create(csv_file, klass, attribute_order, value_parser = nil) 
    parsed_csv = CSV.parse(csv_file, col_sep: "|") 

    records = []  

    ActiveRecord::Base.transaction do 
     parsed_csv.each do |row| 
     record = Hash.new {|h, k| h[k] = []}  
     row.each_with_index do |value, index| 
      record[attribute_order[index]] = value 
     end 
     if value_parser.blank? 
      records << klass.create(record) 
     else 
      value_parser.call(record).each do |parsed_record| 
      records << klass.create(parsed_record) 
      end 
     end 
     end 
    end 
    return records 
    end 

end 

我測試模塊本身: 需要 'spec_helper'

describe CsvParser do 

    it "should create relations" do 
    file = File.new(Rails.root.join('spec/fixtures/files/importrelaties.txt')) 
    Relation.should_receive(:create).at_least(:once) 
    Relation.import_csv(file).should be_kind_of Array 
    end 

    it "should create mutations" do 
    file = File.new(Rails.root.join('spec/fixtures/files/importmutaties.txt')) 
    Mutation.should_receive(:create).at_least(:once)  
    Mutation.import_csv(file).should be_kind_of Array 
    end 

    it "should create strategies" do 
    file = File.new(Rails.root.join('spec/fixtures/files/importplan.txt')) 
    Strategy.should_receive(:create).at_least(:once) 
    Strategy.import_csv(file).should be_kind_of Array 
    end 

    it "should create reservations" do 
    file = File.new(Rails.root.join('spec/fixtures/files/importreservering.txt')) 
    Reservation.should_receive(:create).at_least(:once) 
    Reservation.import_csv(file).should be_kind_of Array 
    end 

end 

回答

4

一些有趣的問題。幾個注意事項:

  1. 你可能不應該在lambda中有一個返回。只要做出最後的聲明[h]。
  2. 如果我正確理解代碼,lambda的第一行和第二行過於複雜。減少他們,使他們更容易閱讀和重構:

    h["is_credit"] = (h['is_credit'] == 'B') # I *think* that will do the same 
    h['sum'] = h['sum'].to_f # Your original code would have left this a string 
    h['sum'] *= -1 unless h['is_credit'] 
    
  3. 它看起來像你的拉姆達不依賴於任何東西(從h除外)外,所以我會單獨進行測試。你甚至可以把它恆定:

    class Mutation < ActiveRecord::Base 
        extend CsvParser # <== See point 5 below 
    
        PARSE_CREDIT_AND_SUM = lambda do |h| 
        h["is_credit"] = (h['is_credit'] == 'B') 
        h['sum'] = h['sum'].to_f 
        h['sum'] *= -1 unless h['is_credit'] 
        [h] 
        end 
    
  4. 不知道的理由,很難說你應該把這個代碼。我的直覺是,它不是CSV解析器的工作(雖然一個好的解析器可能會檢測浮點數並將它們從字符串中轉換出來)保持CSV解析器的可重用性。 (注意:重新閱讀,我認爲你自己已經回答了這個問題 - 這是商業邏輯,與模型綁定在一起。)

  5. 最後,你正在定義和方法CsvParser.create。你並不需要延長CsvParser來訪問它,但如果你在CsvParser有其他的設施,考慮制定CsvParser.create正常模組的方法稱爲像create_from_csv_file

+0

感謝您的透徹,明確的答覆。我遵循你的建議,並能夠測試常量,因爲我可以調用Class :: 。我只是想知道如何測試像這樣的東西,當它是一個方法中的變量時。 – 2012-04-12 10:15:23

+1

如果lambda不能被重構出來,我會以任何其他方法來測試它。如果它是一個非常複雜的lambda,依靠大量的局部變量,那麼這可能表明它想成爲它自己的實例。作爲一般規則,難以測試的方法需要重構! – user208769 2012-04-13 17:29:56

+0

今天我意識到有一種叫做方法對象的東西。它可以像lambda一樣使用,唯一的區別是我可以使用方法的代碼而不是lambda。我認爲它比定義一個lambda作爲一個常量更清潔。所以,而不是傳遞lambda作爲參數我現在使用方法(:) – 2012-04-16 12:10:59