2012-11-29 3 views

Antwort

20

Dies sollte zwei Tests sein. RSpec best practices call for one assertion per test.

describe "#bar" do 
    subject { lambda { Foo.bar } } 

    it { should change { Counter.count }.by 1 } 
    it { should change { AnotherCounter.count }.by 1 } 
end 
+7

Manchmal führt dies zu einer Menge Standardcode (wenn eine Spezifikation eine komplexe Konfiguration erfordert). Oder vielleicht mache ich es einfach falsch :) –

+2

Im Allgemeinen, wenn ich eine Menge Boilerplate tue, versuche ich, meinen Kontext/beschreiben Blöcke zu verfeinern, so dass ich das Setup in vor Blöcken machen kann. Das räumt normalerweise auf. –

+3

Das ist alles in Ordnung für Modell/Unit-Tests, aber was ist mit Feature/Integration Tests, wo es normal ist, viele Behauptungen in einem Test zu machen? – Arcolye

2

Ich ignoriere die besten Praktiken aus zwei Gründen:

  1. Eine Reihe von meinen Tests Regressionstests sind, ich möchte, dass sie schnell laufen, und sie brechen selten. Der Vorteil der Klarheit über genau , was bricht ist nicht riesig, und die Verlangsamung der Refactoring meines Codes , so dass es das gleiche Ereignis mehrmals ausgeführt wird, ist für mich Material.
  2. Ich bin ein bisschen manchmal faul, und es ist einfacher, nicht, dass refactor

Der Weg, dies ich tue, zu tun (wenn ich so tun müssen) ist auf die Tatsache verlassen, dass meine Datenbank startet leer, schreibe ich konnte so dann:

foo.bar 
expect(Counter.count).to eq(1) 
expect(Anothercounter.count).to eq(1) 

meine Datenbank nicht leer In einigen Fällen ist, aber ich entweder weiß, die vor Graf, oder ich kann zählen für die vor explizit testen:

counter_before = Counter.count 
another_counter_before = Anothercounter.count 

foo.bar 

expect(Counter.count - counter_before).to eq(1) 
expect(Anothercounter.count - another_counter_before).to eq(1) 

Finale ly, wenn Sie eine Menge von Objekten überprüfen Sie (ich manchmal tun) tun dies kann als:

before_counts = {} 
[Counter, Anothercounter].each do |classname| 
    before_counts[classname.name] = classname.count 
end 

foo.bar 

[Counter, Anothercounter].each do |classname| 
    expect(classname.count - before_counts[classname.name]).to be > 0 
end 

Wenn Sie mir ähnliche Bedürfnisse haben dies mein einziger Rat wäre dies funktionieren wird, zu tun mit Ihrem die Augen offen - die anderen vorgeschlagenen Lösungen sind eleganter, haben aber unter bestimmten Umständen nur ein paar Nachteile.

11

Wenn Sie den zuvor vorgeschlagenen shorthand/kontextbasierten Ansatz nicht verwenden möchten, können Sie auch so etwas tun, aber seien Sie gewarnt, dass die Erwartung zweimal ausgeführt wird, sodass sie möglicherweise nicht für alle Tests geeignet ist.

it "should increment the count" do 
    expectation = expect { Foo.bar } 
    expectation.to change { Counter.count }.by 1 
    expectation.to change { AnotherCounter.count }.by 1 
end 
+0

Vielen Dank für die Beantwortung der eigentlichen Frage, es wird sehr geschätzt. –

+7

hmmm, es läuft immer noch den Block zweimal. –

+0

Wenn eine der Erwartungen ist, dass ein Fehler ausgelöst wird, funktioniert dies nicht - es schlägt fehl, die Erwartung zuzuweisen – xxjjnn

71

Ich ziehe diese Syntax (rspec 3 oder höher):

it "should increment the counters" do 
    expect { Foo.bar }.to change { Counter,  :count }.by(1).and \ 
         change { AnotherCounter, :count }.by(1) 
end 

Ja, das sind zwei Behauptungen in einem Ort, sondern weil der Block nur einmal ausgeführt wird, kann es die Tests Speedup .

EDIT: Added Aufkantung nach dem .and Syntaxfehler

+0

Dies testet nur die letzte Behauptung und ignoriert alle vorherigen. Siehe meine Antwort für die richtige Kombination von Behauptungen. – somecto

+1

In Rspec 3 funktioniert die obige Methode, wenn Sie den Kompositionsoperator '.and' verwenden (aber Sie können den einzelnen' & 'Alias ​​nicht wie in den rspec-Dokumenten oder dem Operator && wie in der ursprünglichen Antwort beschrieben verwenden). Ich habe die Antwort wie gewünscht bearbeitet. –

+3

tatsächlich, mit rspec 3 Zusammensetzung führt dazu, dass der Block mehrmals ausgeführt wird :(. Seufz. Danke Cargo Cultists für die Forderung, alle zu Integrationstests zu verurteilen, die eine Größenordnung länger dauern als sie brauchen. –

3

Georg Ladermann Syntax zu vermeiden, ist schöner, aber es funktioniert nicht. Die Möglichkeit, mehrere Wertänderungen zu testen, besteht darin, die Werte in Arrays zu kombinieren. Andernfalls entscheidet nur die letzte Änderungsbestätigung über den Test.

Hier ist, wie ich es tun:

it "should increment the counters" do 
    expect { Foo.bar }.to change { [Counter.count, AnotherCounter.count] }.by([1,1]) 
end 

Das funktioniert perfectecly mit dem '.to' -Funktion.

+3

leider tut dies nicht, was Sie denken, dass es ist. Es wird lediglich behauptet, dass die endgültige Zählung [1, 1] ist. Um zu sehen, was ich meine, führen Sie Ihren Test mit einigen bereits vorhandenen Counter-Datensätzen aus. –

+0

siehe [meine Antwort] (http://Stackoverflow.com/a/24591809/173542) für einen funktionierenden change_multiple Matcher für rspec 3 –

+0

@MichaelJohnston Du hast recht. Es funktioniert nicht mit der '.by' Methode, aber es funktioniert mit der '.to' Methode. Tatsächlich beantwortet es die ursprüngliche Frage, ob Änderungen in mehreren Tabellen zu erwarten sind. Außerdem läuft der Block in diesem Fall nur einmal. Ich stimme zu, dass die Beschränkung von Tests auf nur ein Element wirklich suboptimal ist. – somecto

2

Nachdem keine der vorgeschlagenen Lösungen tatsächlich funktioniert, habe ich dies durch Hinzufügen eines change_multiple Matcher erreicht. Dies funktioniert nur für RSpec 3 und nicht 2. *

module RSpec 
    module Matchers 
    def change_multiple(receiver=nil, message=nil, &block) 
     BuiltIn::ChangeMultiple.new(receiver, message, &block) 
    end 
    alias_matcher :a_block_changing_multiple, :change_multiple 
    alias_matcher :changing_multiple,   :change_multiple 

    module BuiltIn 
     class ChangeMultiple < Change 
     private 

      def initialize(receiver=nil, message=nil, &block) 
      @change_details = ChangeMultipleDetails.new(receiver, message, &block) 
      end 
     end 
     class ChangeMultipleDetails < ChangeDetails 
     def actual_delta 
      @actual_after = [@actual_after].flatten 
      @actual_before = [@actual_before].flatten 
      @actual_after.map.with_index{|v, i| v - @actual_before[i]} 
     end 
     end 
    end 
    end 
end 

Anwendungsbeispiel:

it "expects multiple changes despite hordes of cargo cultists chanting aphorisms" do 
    a = "." * 4 
    b = "." * 10 
    times_called = 0 
    expect { 
    times_called += 1 
    a += ".." 
    b += "-----" 
    }.to change_multiple{[a.length, b.length]}.by([2,5]) 
    expect(times_called).to eq(1) 
end 

machen by_at_least und by_at_most Arbeit für change_multiple würde einige zusätzliche Arbeit erfordern.

+0

Das ist schön, aber es ist nicht möglich Assertionen wie 'x' von 1 zu 2 zu ändern,' y' sollte zu 3 wechseln, 'z' sollte von 4 wechseln, und k sollte sich einfach ändern. –

4

Der beste Weg, die ich gefunden habe ist, es zu tun „von Hand“:

counters_before   = Counter.count 
another_counters_before = AnotherCounter.count 
Foo.bar 
expect(Counter.count).to eq (counters_before + 1) 
expect(AnotherCounter.count).to eq (another_counters_before + 1) 

nicht die eleganteste Lösung, aber es funktioniert

21

ich Syntaxfehler habe versucht @MichaelJohnston's solution zu verwenden; das ist die Form, die für mich endlich geklappt:

it "should increment the counters" do 
    expect { Foo.bar }.to change { Counter.count }.by(1) 
    .and change { AnotherCounter.count }.by(1) 
end 

ich schon erwähnt, soll ich mit Ruby 2.2.2p95 - ich weiß nicht, ob diese Version einige subtile Veränderung in Parsing hat, die ich verursacht Fehler zu bekommen, Es scheint nicht, dass jemand anderes in diesem Thread dieses Problem hatte.

+1

Yup. Das hat auch für mich funktioniert. Vielen Dank. – poweratom

+0

Noch ein Daumen nach oben. Arbeitete in der neuesten Version von allem Mitte 2016. Anstelle eines Blocks können Sie auch params wie folgt an die 'change'-Methode übergeben:' um (Counter,: count) .by (1) 'zu ändern, wenn das Ihrer Meinung nach kitzelt. –