2013-04-12 5 views
5

Ich schreibe einen kleinen Job-Scheduler in Python. Der Scheduler kann eine Reihe von Callables plus Abhängigkeiten erhalten und sollte die Callables ausführen, um sicherzustellen, dass keine Task vor einem seiner Vorgänger ausgeführt wird.Testen einer Funktion, die nicht-deterministische Ergebnisse mit Python Unit Test zurückgeben kann

Ich versuche, einen Test-driven Ansatz zu folgen, und ich bin in ein Problem getestet, das Abhängigkeitshandhabung prüft. Mein Test-Code sieht wie folgt aus:

def test_add_dependency(self): 
    """Tasks can be added with dependencies""" 
    # TODO: Unreliable test, may work sometimes because by default, task 
    #  running order is indeterminate. 
    self.done = [] 
    def test(id): 
     self.done.append("Test " + id) 
    s = Schedule() 
    tA = Task("Test A", partial(test, "A")) 
    tB = Task("Test B", partial(test, "B")) 
    s.add_task(tA) 
    s.add_task(tB) 
    s.add_dependency(tA, tB) 
    s.run() 
    self.assertEqual(self.done, ["Test B", "Test A"]) 

Das Problem ist, dass dieser Test (manchmal) arbeitete, noch bevor ich die Abhängigkeit Behandlungscode hinzugefügt. Dies liegt daran, dass die Spezifikation nicht angibt, dass Aufgaben in einer bestimmten Reihenfolge ausgeführt werden müssen. Daher ist die richtige Reihenfolge eine vollkommen gültige Wahl, selbst wenn die Abhängigkeitsinformation ignoriert wird.

Gibt es eine Möglichkeit, Tests zu schreiben, um diesen "zufälligen" Erfolg zu vermeiden? Es scheint mir, dass dies eine ziemlich häufige Art von Situation ist, insbesondere wenn man den Test-getriebenen Ansatz "Schreibe keinen Code ohne einen fehlgeschlagenen Test" anwendet.

+0

Ich kenne Scheduler, die absichtlich eine Randomisierung verwenden (normalerweise eingeschaltet durch ein "Testmodus" -Flag), wenn eine von vielen möglichen Auswahlen ausgewählt wird. Damit könnten Sie Ihren Test einige Male durchführen und prüfen, ob * alle * Ergebnisse korrekt sind. Dies ist jedoch keine echte Lösung, da Sie nie wissen, wie viele Runden Sie benötigen. – rainer

+0

Obwohl Vorschläge für Tests sehr nützlich sind, denke ich, dass mein wirkliches Problem in der "testgetriebenen" Idee liegt, dass ich keinen Code schreiben sollte, wenn ich keinen fehlerhaften Test habe. In diesem Fall hinterlässt die Spezifikation bewusst ein Maß an Flexibilität, das das Schreiben eines solchen Tests unmöglich macht. Dies ist immer, wo ich mit dem Test-getriebenen Ansatz festhalte, aber normalerweise bin ich nur zu faul, um einen guten Test zu schreiben, nicht weil ich nicht kann :-) –

Antwort

1

Diese Strategie funktioniert viel von der Zeit:

Zuerst beseitigen jede externe Quelle der Entropie (Thread Pool einen einzigen Thread zu verwenden; alle RNGs verspotten mit bereits geimpften PRNGs etc.) Dann trainieren Sie Ihre Test wiederholt jede Kombination von Ausgängen, ändert nur die Eingaben an die Maschinen im Test zu produzieren:

from itertools import permutations 
def test_add_dependency(self): 
    """Tasks can be added with dependencies""" 
    for p in permutations("AB"): 
     self.done = [] 
     def test(id): 
      self.done.append("Test " + id) 
     s = Schedule(threads=1) 
     tasks = {id: Task("Test " + id, partial(test, id)) for id in "AB"} 
     s.add_task(tasks['A']) 
     s.add_task(tasks['B']) 
     s.add_dependency(tasks[p[0]], tasks[p[1]]) 
     s.run() 
     self.assertEqual(self.done, ["Test " + p[1], "Test " + p[0]]) 

Dieser Test schlägt fehl, wenn Schedule die Informationen von add_dependency zu verwenden, schlägt fehl, da dies die einzige Quelle der Entropie (dh Informationen), die sich zwischen den Testläufen unterscheiden.

+0

Guter Punkt. Meine Sorge hier ist der Test-getriebene Entwicklungsansatz, wo ich "einen Fehler finden muss" (Abhängigkeitsinformationen werden ignoriert), bevor ich Code schreiben kann. Aber ich vergesse die Tatsache, dass dies Unit-Tests sind, keine Black-Box-Tests. So kann ich die Interna des Schedulers verspotten, um mich ausschließlich auf die Abhängigkeitsverarbeitung zu konzentrieren. Vielen Dank! –

1

Eine Option wäre die Verwendung einer anderen deterministischen Version der Klasse Schedule (oder eine Option hinzufügen, um die vorhandene Version deterministisch zu machen) zu Testzwecken, die jedoch den Zweck des Komponententests vereiteln könnte.

Eine andere Möglichkeit wäre, Testfälle für nicht-deterministische Ergebnisse nicht zu schreiben.


Im Allgemeinen ist die Antwort auf Ihre Frage aber ...

Gibt es eine Möglichkeit des Schreibens Tests dieser Art von „zufälligen“ Erfolg zu vermeiden?

... ist wahrscheinlich "nein", andere sind besonders wachsam, wenn man sie schreibt. Wenn Sie jedoch die Fähigkeit haben, wachsam genug zu sein, um fragwürdige Testfälle zu vermeiden, und diese Wachsamkeit beim Schreiben des Codes angewandt haben, dann brauchen Sie wohl nicht einmal Unit-Tests. ;-)

Wenn der Unit-Unit-Test Bugs im Code aufspüren soll, wie erkennen Sie Fehler in den Unit-Tests?

Sie könnten 'Meta' Unit Tests für Ihre Komponententests schreiben, aber wie finden Sie Fehler in den 'Meta' Unit Tests? Und so weiter ...

Nun, das soll nicht heißen, dass Komponententests nicht nützlich sein können, aber sie reichen nicht aus, um "zu beweisen", dass der Code "korrekt" ist. In der Praxis finde ich, dass Peer-basierte Code-Reviews ein viel effektiveres Mittel zur Erkennung von Fehlern im Code sind.

+0

Hinzufügen einer Option zum Scheduler, um eine Bestellung zu erzwingen wäre sicherlich eine Option. Daran habe ich nicht gedacht - danke. –

2

Sie befinden sich in der Situation eines jeden Forschers, der eine Sammlung von unvollständigen Daten betrachtet und versucht zu sagen, ob die Hypothese darüber wahr ist oder nicht.

Wenn die Ergebnisse zwischen den Läufen variieren, erhalten Sie bei vielen Wiederholungen ein Beispiel, auf das Sie Statistiken anwenden können, um zu entscheiden, ob es funktioniert oder nicht. Wenn jedoch ein Batch von Läufen ähnliche Ergebnisse liefert, aber ein anderer Batch an einem anderen Tag ein anderes Ergebnis liefert, hängt Ihr Nicht-Determinismus von Ereignissen ab, die sich außerhalb des Programms selbst befinden, und Sie müssen einen Weg finden um sie zu kontrollieren, im Idealfall, damit sie die Chance maximieren, einen schlechten Algorithmus auszulösen.

Dies sind die Kosten der Nicht-Determinismus; Sie müssen auf Statistiken zurückgreifen und Sie müssen die Statistiken richtig machen. Sie müssen in der Lage sein, die Hypothese mit einem gewissen Konfidenzniveau zu akzeptieren und auch die Nullhypothese abzulehnen. Dies erfordert weniger Stichproben, wenn Sie die Varianz der Ergebnisse maximieren können. haben eine variierende CPU-Last oder IO-Interrupts oder planen eine Task mit zufälligen Einschlafen.

Herauszufinden, was ein solcher Scheduler betrifft, wäre wahrscheinlich ratsam, um einen lohnenden Test trotzdem zu definieren.

1

Ich würde empfehlen, dass Sie bestimmen, was getestet werden muss, bevor Sie den Test schreiben.

In Ihrem obigen Codebeispiel wird getestet, dass eine bestimmte Abfolge von Tasks vom Scheduler generiert wird, obwohl die tatsächliche Sequenz gemäß Ihrer Beschreibung des Schedulers nicht deterministisch ist, so dass der Test nicht wirklich funktioniert jede Gewissheit über den Code: Manchmal wird es passieren, manchmal wird es nicht, und wenn es passiert, wird es nur zufällig sein.

auf der anderen Seite, ein wertvoller Test könnte die Anwesenheit (oder Abwesenheit) von Aufgaben in den Ergebnissen zu behaupten, ohne etwas über ihre Position zu behaupten: „in der Serie ist“ vs „in Feldposition ist“