2012-04-11 13 views
5

Sto creando una funzione di importazione che importa file CSV in più tabelle. Ho creato un modulo chiamato CsvParser che analizza un file CSV e crea record. I miei modelli che ricevono le azioni create estende lo CsvParser. Effettuano una chiamata a CsvParser.create e passano l'ordine dell'attributo corretto e un lambda opzionale chiamato value_parser. Questo lambda trasforma i valori in un hash in un formato preffered.Test a 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 

La ragione per cui sto usando un lambda al posto di controlli all'interno del metodo CsvParser.create è perché il lambda è come una regola di business che appartiene a questo modello.

La mia domanda è come dovrei provare questo lambda. Dovrei testarlo nel modello o nel CsvParser? Dovrei testare il lambda stesso o il risultato di un array del metodo self.import? Forse dovrei fare un'altra struttura di codice?

mio CsvParser appare come segue:

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 

sto testando il modulo stesso: richiedono '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 

risposta

4

Alcune domande interessanti. Un paio di note:

  1. Probabilmente non si dovrebbe avere un ritorno all'interno del lambda. Basta fare l'ultima affermazione [h].
  2. Se ho capito bene il codice, la prima e la seconda riga del tuo lambda sono eccessivamente complicate. Ridurre loro per renderli più leggibili e più facili da refactoring:

    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. Sembra che la tua lambda non dipende da qualcosa di esterno (a parte h), così ho iniziato ad esaminare separatamente. Si potrebbe anche fare una costante:

    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. Senza conoscere la logica, è difficile dire dove si dovrebbe mettere questo codice. Il mio istinto è che non è il lavoro del parser CSV (anche se un buon parser può rilevare numeri in virgola mobile e convertirli da stringhe?) Mantenere il parser CSV riutilizzabile. (Nota: rilettura, penso che tu abbia risposto a questa domanda tu stesso: è una logica di business, legata al modello. Vai al tuo istinto!)

  5. Infine, stai definendo e il metodo CsvParser.create. Non è necessario estendere CsvParser per ottenere l'accesso ad esso, anche se si dispone di altre strutture in CsvParser, considerare la possibilità di un metodo CsvParser.create modulo normale chiamata qualcosa come create_from_csv_file

+0

Grazie per la vostra, risposta chiara approfondita. Ho seguito il tuo consiglio ed ho potuto testare la costante perché potevo chiamare Class :: . Mi chiedo solo come dovrei provare qualcosa di simile quando sarebbe una variabile in un metodo. –

+1

Se il lambda non potesse essere refactored, lo testerei come qualsiasi altro metodo. Se si tratta di un lambda molto complicato, basato su molte variabili locali, questo potrebbe indicare che vuole essere la sua istanza. Come regola generale, i metodi difficili da testare necessitano di un refactoring! – user208769

+0

Oggi mi sono reso conto che esiste un qualcosa chiamato oggetto metodo. Può essere usato allo stesso modo di un lambda con l'unica differenza che posso usare il codice di un metodo invece di un lambda.Penso che sia più pulito che definire una lambda come costante. Quindi, invece di passare il lambda come parametro, ora uso il metodo (: ) –