2010-01-07 5 views
25

Wir haben eine asynchrone Aufgabe, die eine potenziell lang laufende Berechnung für ein Objekt ausführt. Das Ergebnis wird dann im Objekt zwischengespeichert. Um mehrere Aufgaben zu wiederholen, die gleiche Arbeit zu vermeiden, haben wir mit einem Atom SQL Update Sperren:Simulieren von Wettlaufbedingungen in RSpec-Komponententests

UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0 

Die Verriegelung nur für die asynchrone Aufgabe. Das Objekt selbst kann vom Benutzer noch aktualisiert werden. Wenn dies geschieht, sollte eine nicht abgeschlossene Aufgabe für eine alte Version des Objekts die Ergebnisse verwerfen, da sie wahrscheinlich nicht mehr aktuell sind. Dies ist auch recht einfach mit einem Atom SQL-Update zu tun:

UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1 

Wenn das Objekt aktualisiert wurde, wird seine Version nicht überein, und so werden die Ergebnisse verworfen werden.

Diese beiden atomaren Updates sollten alle möglichen Rennbedingungen behandeln. Die Frage ist, wie das in Unit-Tests zu überprüfen ist.

Der erste Semaphor ist einfach zu testen, da es nur darum geht, zwei verschiedene Tests mit den zwei möglichen Szenarien einzurichten: (1) wo das Objekt gesperrt ist und (2) wo das Objekt nicht gesperrt ist. (Wir müssen die Atomizität der SQL-Abfrage nicht testen, da dies in der Verantwortung des Datenbankherstellers liegt.)

Wie testet man den zweiten Semaphor? Das Objekt muss einige Zeit nach dem ersten Semaphor, aber vor dem zweiten von einem Dritten geändert werden. Dies würde eine Ausführungspause erfordern, so dass die Aktualisierung zuverlässig und konsistent durchgeführt werden kann, aber ich kenne keine Unterstützung für das Einfügen von Haltepunkten mit RSpec. Gibt es eine Möglichkeit, dies zu tun? Oder gibt es eine andere Technik, die ich übersehen habe, um solche Rassenbedingungen zu simulieren?

Antwort

26

Sie können eine Idee aus der Elektronikfertigung ausleihen und Testhaken direkt in den Produktionscode eingeben. Genauso wie eine Leiterplatte mit speziellen Stellen für Testgeräte hergestellt werden kann, um die Schaltung zu steuern und zu prüfen, können wir das Gleiche mit dem Code tun.

Angenommen, wir haben einige Code eine Zeile in die Datenbank einfügen:

class TestSubject 

    def insert_unless_exists 
    if !row_exists? 
     insert_row 
    end 
    end 

end 

Aber dieser Code auf mehreren Computern ausgeführt wird. Es gibt eine Race-Bedingung, da ein anderer Prozess die Zeile zwischen unserem Test und unserem Insert einfügen kann, was zu einer DuplicateKey-Ausnahme führt. Wir möchten testen, dass unser Code die Ausnahme verarbeitet, die aus dieser Race-Bedingung resultiert. Um dies zu tun, muss unser Test die Zeile nach dem Anruf an row_exists? aber vor dem Anruf an insert_row einfügen. Also lassen Sie uns direkt ein Testhaken hinzufügen:

class TestSubject 

    def insert_unless_exists 
    if !row_exists? 
     before_insert_row_hook 
     insert_row 
    end 
    end 

    def before_insert_row_hook 
    end 

end 

Wenn im freien Lauf, der Haken nichts tut, außer essen ein klein wenig CPU-Zeit auf. Aber wenn der Code für die Race-Bedingung getestet, die Testaffen Patches before_insert_row_hook:

class TestSubject 
    def before_insert_row_hook 
    insert_row 
    end 
end 

Ist das nicht schlau? Wie eine parasitische Wespenlarve, die den Körper einer ahnungslosen Raupe entführt hat, entführte der Test den zu testenden Code, so dass er genau den Zustand schafft, den wir testen müssen.

Diese Idee ist so einfach wie der XOR-Cursor, ich vermute, viele Programmierer haben es unabhängig erfunden. Ich habe festgestellt, dass es generell nützlich ist, um Code mit Rennbedingungen zu testen. Ich hoffe, es hilft.

+1

A-ha. Das würde es tun.Anstatt einen expliziten Hook hinzuzufügen, kann ich einfach 'alias_method_chain' verwenden, um die Funktionalität einer Methode zu erweitern, die sowieso zwischen den beiden Semaphoren aufgerufen werden muss - die lang andauernde Aufgabe. – Ian

+0

Ian, Das würde es tun. –

+4

+1 für die Verwendung von parasitischen Wespenlarven in Ihrem Gleichnis. – aronchick