Frage

Seit ich mit rspec begonnen, ich habe ein Problem mit dem Begriff der Armaturen hat. Meine Hauptanliegen sind dies:

  1. Ich verwende Tests überraschende Verhalten zu offenbaren. Ich bin nicht immer klug genug, um jede mögliche Kante Fall für die Beispiele aufzuzählen Ich teste. hartcodierte Befestigungen verwendet, scheint zu begrenzen, weil es nur mein Code mit den sehr speziellen Fällen Tests, die ich mir vorgestellt habe. (Zugegeben, meine Phantasie auch in Bezug auf welche Fälle I-Test zu begrenzen.)

  2. Ich verwende Tests als eine Form der Dokumentation für den Code. Wenn ich Werte hartcodierte Befestigung, ist es schwer zu zeigen, was ein bestimmte Test zu demonstrieren versucht. Zum Beispiel:

    describe Item do
      describe '#most_expensive' do
        it 'should return the most expensive item' do
          Item.most_expensive.price.should == 100
          # OR
          #Item.most_expensive.price.should == Item.find(:expensive).price
          # OR
          #Item.most_expensive.id.should == Item.find(:expensive).id
        end
      end
    end
    

    die erste Methode gibt den Leser keinen Hinweis darauf, was der teuerste Artikel ist nur, dass der Preis ist 100. Alle drei Methoden, um die Leser bitten, sie auf dem Glauben zu nehmen, dass die Halterung :expensive die teuerste in fixtures/items.yml aufgeführt ist. Ein unvorsichtiger Programmierer könnte Tests durchbrechen, indem eine Item in before(:all) Schaffung oder durch eine andere Befestigung in fixtures/items.yml eingefügt wird. Wenn das eine große Datei ist, könnte es lange dauern, um herauszufinden, was das Problem ist.

Eine Sache, die ich zu tun habe angefangen, ist eine #generate_random Methode, um alle meine Modelle hinzufügen. Diese Methode ist nur verfügbar, wenn ich meine specs leite. Zum Beispiel:

class Item
  def self.generate_random(params={})
    Item.create(
      :name => params[:name] || String.generate_random,
      :price => params[:price] || rand(100)
    )
  end
end

(Die spezifischen Details, wie ich dies tun, sind eigentlich ein bisschen sauberer. Ich eine Klasse, die die Erzeugung und Bereinigung aller Modelle behandelt, aber dieser Code ist klar genug für mein Beispiel.) Also in dem obigen Beispiel I könnte wie folgt testen. Eine Warnung für schwache Nerven: mein Code stützt sich stark auf den Einsatz von before(:all):

describe Item do
  describe '#most_expensive' do
    before(:all) do
      @items = []
      3.times { @items << Item.generate_random }
      @items << Item.generate_random({:price => 50})
    end

    it 'should return the most expensive item' do
      sorted = @items.sort { |a, b| b.price <=> a.price }
      expensive = Item.most_expensive
      expensive.should be(sorted[0])
      expensive.price.should >= 50      
    end
  end
end

Auf diese Weise meine Tests besser überraschendes Verhalten zeigen. Wenn ich Daten auf diese Weise erzeugen, stoße ich gelegentlich auf einen Rand Fall, in dem mein Code verhält sich nicht wie erwartet, aber das würde ich nicht gefangen, wenn ich nur strahlende Leuchten wurde. Zum Beispiel im Fall von #most_expensive, wenn ich den speziellen Fall, in dem mehrere Artikel zu den teuersten Preis teilen zu handhaben vergessen, wäre mein Test gelegentlich bei der ersten should scheitern. die nicht-deterministisch zu sehen Ausfälle in AutoSpec würde mir Ahnung, dass etwas falsch war. Wenn ich nur strahlende Leuchten wurden, könnte es viel länger dauern einen solchen Fehler zu entdecken.

Meine Tests machen auch einen etwas besseren Job in Code zu demonstrieren, was das erwartete Verhalten ist. Mein Test macht deutlich, dass sortiert ein Array von sortierten Elementen, um nach Preis in absteigender Reihenfolge. Da ich #most_expensive erwarten, dass er gleich das erste Element des Arrays, es ist noch deutlicher, was das erwartete Verhalten von most_expensive ist.

Also, das ist eine schlechte Praxis? Ist meine Angst vor Leuchten ein irrationaler ein? Schreibt eine generate_random Methode für jedes Modell zu viel Arbeit? Oder funktionierts?

War es hilfreich?

Lösung

Dies ist eine Antwort auf Ihren zweiten Punkt:

  

(2) Ich verwende Tests als eine Form der Dokumentation für den Code. Wenn ich Werte hartcodierte Befestigung, es ist schwer zu zeigen, was ein bestimmte Test zu demonstrieren versucht.

Ich bin damit einverstanden. Im Idealfall sollten spec Beispiele verständlich selbst sein. Verwendung von Armaturen ist problematisch, weil es die Voraussetzungen des Beispiels von seinen erwarteten Ergebnissen aufteilt.

Aus diesem Grund haben viele RSpec Benutzer gestoppt Vorrichtungen insgesamt verwenden. Stattdessen baut die benötigten Objekte in dem spec Beispiel selbst.

describe Item, "#most_expensive" do
  it 'should return the most expensive item' do
    items = [
      Item.create!(:price => 100),
      Item.create!(:price => 50)
    ]

    Item.most_expensive.price.should == 100
  end
end

Wenn Ihr am Ende mit viel Standardcode für die Objekterstellung, sollten Sie einen Blick auf einige der vielen Testobjekt Fabrik Bibliotheken nehmen, wie factory_girl , Maschinist oder < a href = "http://replacefixtures.rubyforge.org/" rel = "nofollow noreferrer"> FixtureReplacement .

Andere Tipps

Ich bin überrascht, niemand hat in diesem Thema oder in einer Jason Baker verbunden erwähnt Monte Carlo Testing . Das ist das einzige Mal, dass ich ausgiebig randomisierten Testeingaben verwendet haben. Allerdings war es sehr wichtig, den Test reproduzierbar zu machen, indem eine konstante Samen für den Zufallszahlengenerator für jeden Testfall ist.

Wir dachten über diese viel an einem neuen Projekt von mir. Am Ende entschieden wir uns auf zwei Punkte:

  • Wiederholbarkeit von Testfällen ist von größter Bedeutung. Wenn Sie einen Zufallstest schreiben müssen, hergestellt werden sie ausführlich dokumentieren, denn wenn / wenn es fehlschlägt, müssen Sie genau wissen, warum.
  • Verwenden von Zufälligkeit als eine Krücke für Codeabdeckung bedeutet entweder Sie nicht eine gute Abdeckung haben, oder Sie nicht verstehen, die Domäne genug, um zu wissen, was repräsentative Testfälle ausmacht. Finde heraus, was wahr ist und fixieren Sie es entsprechend.

In der Summe Zufälligkeit kann oft mehr Ärger als es wert ist. Überlegen Sie sorgfältig, ob Sie vorhaben, es richtig zu verwenden, bevor Sie abdrücken. Wir entschieden uns schließlich, dass zufällige Testfälle eine schlechte Idee, im allgemeinen waren und sparsam verwendet werden, wenn überhaupt.

Es gibt viele gute Informationen bereits gebucht wurde, aber siehe auch: Fuzzing . Wort auf der Straße ist, dass Microsoft verwendet diesen Ansatz auf viele ihrer Projekte.

Meine Erfahrung mit dem Testen ist meist mit einfachen Programmen geschrieben in C / Python / Java, also bin ich nicht sicher, ob dies ganz zutreffend ist, aber wenn ich ein Programm, das jede Art von Benutzereingaben akzeptieren kann, habe ich immer schließen ein Test mit zufälligen Eingangsdaten oder Daten zumindest Eingabe durch den Computer in einer unvorhersehbaren Art und Weise erzeugt, weil man nie Annahmen darüber treffen kann, welche Benutzer eingeben werden. Oder, na ja, Sie können , aber wenn Sie tun, dann einige Hacker, der nicht diese Annahme nicht machen kann auch einen Fehler finden, die Sie völlig übersehen. Maschinenerzeugten Eingang ist der beste (nur?) Weg, den ich kenne, die menschliche Tendenz zu halten vollständig aus den Testverfahren. Natürlich, um einen fehlgeschlagenen Test zu reproduzieren haben Sie so etwas wie das Speichern der Testeingabe in eine Datei oder druckt es aus zu tun (wenn es Text ist), bevor der Test ausgeführt wird.

Random Test ist eine schlechte Praxis eine lange Sie haben keine Lösung für die Orakel Problem , das heißt, die Bestimmung, welche das erwartete Ergebnis Ihrer Software seine Eingabe gegeben ist.

Wenn Sie das Orakel Problem gelöst, können Sie noch einen Schritt weiter als einfache Zufallseingang Generation erhalten. Sie können Eingabeverteilungen, so dass bestimmte Teile des Software ausgeübt bekommen mehr als mit einem einfachen Zufall wählen.

Sie wechseln dann von zufälligen Tests auf statistische Tests.

if (a > 0)
    // Do Foo
else (if b < 0)
    // Do Bar
else
    // Do Foobar

Wenn Sie a und b zufällig in int Bereich auswählen, können Sie Foo 50% der Zeit ausüben, Bar 25% der Zeit und Foobar 25% der Zeit. Es ist wahrscheinlich, dass Sie mehr Fehler in Foo als in Bar oder Foobar finden werden.

Wenn Sie wählen a so dass es negativ ist 66,66% der Zeit, Bar und Foobar bekommt mehr als bei Ihrer ersten Verteilung ausgeübt. Tatsächlich sind die drei Zweige jeweils 33,33% der Zeit ausgeübt erhalten.

Natürlich, wenn Ihr beobachtet Ergebnis als Ihre erwartete Ergebnis unterschiedlich ist, haben Sie alles zu protokollieren, die nützlich sein kann, um den Fehler zu reproduzieren.

Ich würde vorschlagen, einen Blick auf Maschinist mit:

  

http://github.com/notahat/machinist/tree/master

Maschinist generiert Daten für Sie, aber es ist wiederholbar, so dass jeder Testlauf hat die gleichen Zufallsdaten.

Sie könnten etwas Ähnliches tun, indem Sie den Zufallszahlengenerator konsequent Aussaat.

Ein Problem mit zufällig generierten Testfällen ist, dass die Antwort der Validierung von Code berechnet werden sollte und man kann nicht sicher sein, es keine Fehler hat:)

Sie können auch dieses Thema finden Sie unter: Testen mit zufälligen Eingaben Best Practices .

Die Wirksamkeit eines solchen Tests hängt in hohem Maße von der Qualität der Zufallszahlengenerator verwenden Sie und wie korrekt ist der Code, der RNG-Ausgabe in Testdaten übersetzt.

Wenn die RNG nie Werte verursacht Code erzeugen in einigen Rande Fall Zustand erhalten werden Sie diesen Fall nicht abgedeckt haben. Wenn Ihr Code, die RNG Ausgang in dem Eingang des Codes übersetzen Sie defekte testen, ist es, dass selbst bei einem guten Generator passieren kann man noch nicht die Kante alle Fälle schlägt.

Wie werden Sie für das testen?

Das Problem mit Zufälligkeit in Testfällen ist, dass der Ausgang ist, na ja, zufällig.

Die Idee hinter Tests (insbesondere Regressionstests) ist zu überprüfen, dass nichts gebrochen ist.

Wenn Sie etwas finden, das gebrochen ist, müssen Sie diesen Test von nun an jedes Mal schließen, sonst werden Sie nicht eine konsistente Reihe von Tests haben. Auch, wenn Sie einen zufälligen Test ausführen, das funktioniert, dann müssen Sie diesen Test enthalten, weil ihre möglich, dass Sie den Code brechen können, so dass der Test nicht besteht.

Mit anderen Worten, wenn Sie einen Test haben, die im laufenden Betrieb erzeugten Zufallsdaten verwendet, ich denke, das ist eine schlechte Idee. Wenn jedoch verwenden Sie eine Reihe von Zufallsdaten, die Sie dann gespeichert und wieder verwendet, kann dies eine gute Idee sein. Dies könnte in Form eines Satzes von Samen für einen Zufallszahlengenerator nehmen.

Diese Speicherung der erzeugten Daten können Sie die ‚richtige‘ Antwort auf diese Daten finden.

Also, ich würde mit Zufallsdaten empfiehlt Ihr System zu erkunden, sondern definierte Daten in Ihren Tests verwenden (die ursprünglich zufällig generierten Daten wurden möglicherweise)

Die Verwendung von Zufallstestdaten ist eine ausgezeichnete Praxis - hartcodierte Testdaten prüft nur die Fälle explizit gedacht, während Zufallsdaten spült Ihre impliziten Annahmen, die falsch sein könnte

.

ich empfehlen die Verwendung von Factory Girl und ffaker für diese hoch. (Verwenden Sie niemals Rails Vorrichtungen für etwas unter keinen Umständen.)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top