2010-07-22 8 views
3

Je le test suivant, avec deux blocs presque identiques. Maintenant, je cherche des moyens de refactoriser cela proprement.rspec refactoring?

Le test:

context "with the d1 configuration" do 
    before (:each) do 
    # send a message 
    @envelope = Factory(:envelope, :destination => '32495xxxxxx', :message => 'Message sent by d1') 
    @distributor = Distributor.find_by_name(Distributor::D1) 
    @result = @envelope.send_to(@distributor) 
    end 
    it "should created a new sms-message" do 
    @envelope.sent_messages.size.should == 1 
    end 

    it "should have created one sms-message linked to the envelope and distributor" do 
    sms = @envelope.sent_messages.find_by_distributor_id(@distributor.id) 
    sms.should be_instance_of(SentMessage) 
    sms.external_message_id.should_not == nil 
    sms.sent_message_status_id.should == SentMessageStatus::IN_PROGRESS 
    end 

    it "should add a logline for the creation of the sms-message" do 
    @envelope.log_lines.size.should == 2 
    @envelope.log_lines.last.message.should =~ /^Sent message/ 
    end 
end 


context "with the correct d2 configuration" do 
    before (:each) do 
    # send a message 
    @envelope = Factory(:envelope, :destination => '32495xxxxxx', :message => 'Message sent by d2') 
    @distributor = Distributor.find_by_name(Distributor::D2) 
    @result = @envelope.send_to(@distributor) 
    end 
    it "should created a new sms-message" do 
    @envelope.sent_messages.size.should == 1 
    end 

    it "should have created one sms-message linked to the envelope and distributor" do 
    sms = @envelope.sent_messages.find_by_distributor_id(@distributor.id) 
    sms.should be_instance_of(SentMessage) 
    sms.external_message_id.should_not == nil 
    sms.sent_message_status_id.should == SentMessageStatus::IN_PROGRESS 
    end 

    it "should add a logline for the creation of the sms-message" do 
    @envelope.log_lines.size.should == 2 
    @envelope.log_lines.last.message.should =~ /^Sent message/ 
    end 
end 

Comme vous pouvez le dire, deux blocs de code identiques, chacun pour un distributeur différent, D1 et D2 (dans notre projet, ils ont des noms plus significatifs :)) - et maintenant i besoin d'ajouter un troisième distributeur. Comment vais-je à ce sujet?

Je peux boucler sur un tableau contenant les parties changeantes (dans ce cas: nom du distributeur et contenu du message). Mais puis-je aussi changer le nom du test?

Quelles sont les meilleures approches ici? Est-il possible de faire une sorte de modèle de test, où vous pouvez remplir certaines valeurs et l'exécuter?

Répondre

2

Oui, vous pouvez parcourir un tableau/hachage plein d'exemples et oui vous pouvez renommer les contextes en fonction de cela, mais vous devez être conscient des problèmes de portée - par exemple un contexte est une portée de niveau classe, mais un test est une instance. Vous devez donc configurer ces éléments dans instance-variables dans la section "setup" d'un contexte. Je l'ai principalement fait avec unit: test + shoulda (plutôt que rspec) donc j'ai peut-être bousillé les règles de portée, mais elles devraient être similaires

Note: Je n'ai pas testé le code ci-dessous, il peut donc être en proie à ces questions ...

# name this better than I have  
CONFIGS = {'d1' => {:name => Distributor::D1 
        :destination => '32495xxxxxx', 
        :message => 'd1 message'}, 
      'd2' => {:name => Distributor::D2 
        :destination => '98765xxxxxx', 
        :message => 'd2 message'} 
      } # etc 

CONFIGS.each do |display_name, dist_hash| 
    context "with the #{display_name} configuration" do 
    before (:each) do 
     # scope the value-hash here to make it available to test-cases 
     # (you don't have to if you're just using it in the setup section) 
     @dist_hash = dist_hash 
     # send a message 
     @envelope = Factory(:envelope, :destination => @dist_hash[:destination], :message => @dist_hash[:message]) 
     @distributor = Distributor.find_by_name(@dist_hash[:name]) 
     @result = @envelope.send_to(@distributor) 
    end 
    it "should created a new sms-message" do 
     @envelope.sent_messages.size.should == 1 
    end 

    it "should have created one sms-message linked to the envelope and distributor" do 
     sms = @envelope.sent_messages.find_by_distributor_id(@distributor.id) 
     sms.should be_instance_of(SentMessage) 
     sms.external_message_id.should_not == nil 
     sms.sent_message_status_id.should == SentMessageStatus::IN_PROGRESS 
    end 

    it "should add a logline for the creation of the sms-message" do 
     @envelope.log_lines.size.should == 2 
     @envelope.log_lines.last.message.should =~ /^Sent message/ 
    end 
    end 
end 
+0

Ceci est une très bonne suggestion pour supprimer le code en double! – nathanvda

4

J'ai eu une séance de programmation en binôme avec un collegue plus expérimenté à moi, et ensemble, nous sommes arrivés avec la solution suivante.

Nous avons défini un comportement partagé d'abord:

subject {@envelope} 
let(:the_sent_message){ @envelope.sent_messages.find_by_distributor_id(@distributor.id)} 

shared_examples_for "a typical sent envelope" do 
    it{should have(1).sent_messages } 
    it{should have(2).log_lines  } 
end 

shared_examples_for "a successful delivery" do 
    it("should have 1 IN_PROGRESS sms-message") { the_sent_message.should be_in_progress } 

    it "should have 1 sms-message with external ref" do 
    the_sent_message.external_message_id.should_not == nil 
    end 

    it "should log the delivery success" do 
    @envelope.log_lines.last.message.should =~ /^Sent message/ 
    end 
end 

shared_examples_for "a failing delivery" do 
    it("should have 1 FAILED sms-message") { the_sent_message.should be_failed } 

    it "should have 1 sms-message and no external ref" do 
    the_sent_message.external_message_id.should == nil 
    end 

    it "should log the delivery failure" do 
    @envelope.log_lines.last.message.should =~ /^Failed to send/ 
    end 
end 

et les essais deviennent de façon plus lisible!

context "delivered by d1" do 
    before do 
    @distributor = Distributor.find_by_name(Distributor::D1) 

    send_a_test_envelope_to(@distributor) 
    end 

    it_should_behave_like "a typical sent envelope" 
    it_should_behave_like "a successful delivery" 
end 

context "delivered by d2" do 
    before do 
    @distributor = Distributor.find_by_name(Distributor::D2) 

    send_a_test_envelope_to(@distributor) 
    end 

    it_should_behave_like "a typical sent envelope" 
    it_should_behave_like "a successful delivery" 
end 

et nous avons également extrait la méthode suivante

def send_a_test_envelope_to(distributor) 
    @envelope = Factory(:envelope, :destination => '32495xxxxxx', :message => "Message sent by #{@distributor.name}") 
    @envelope.send_to(distributor) 
end 

Maintenant, je pouvais encore appliquer la réponse suggérée @Taryn proposée, mais je ne suis pas certain qu'il est vraiment plus nécessaire.

+0

Ya - et celui-ci est beaucoup plus agréable et beaucoup plus semblable à rspec :) Mieux vaut définir un ensemble de comportements communs que les choses devraient suivre! –