Ich schreibe Komponententests für eine Logik höherer Ordnung, die vom Schreiben in eine SQLite3-Datenbank abhängt. Dazu verwende ich twisted.trial.unittest
und twisted.enterprise.adbapi.ConnectionPool
.
Ich kann eine persistente sqlite3-Datenbank erstellen und darin Daten speichern. Mit sqlitebrowser kann ich überprüfen, ob die Daten wie erwartet erhalten wurden.
Das Problem besteht darin, dass Aufrufe von t.e.a.ConnectionPool.run*
(z. B. runQuery
) eine leere Menge von Ergebnissen zurückgeben, jedoch nur, wenn sie innerhalb von TestCase
aufgerufen werden.
Das Problem tritt nur innerhalb von Twisteds trial
framework auf. Mein erster Versuch beim Debugging war, den Datenbankcode aus dem Komponententest zu ziehen und ihn in ein unabhängiges Test / Debug-Skript zu schreiben. Das Skript funktioniert wie erwartet , während der Unit-Test-Code nicht funktioniert (siehe Beispiele unten).
Dies ist das Skript, das zum Initialisieren der Datenbank verwendet wird. Diese Datei enthält keine (offensichtlichen) Fehler.
%Vor% Dies ist die Einheitentestklasse, die unerwartet fehlschlägt. TestStateManagement.test_db_clean
übergibt, gab an, dass die Tabellen ordnungsgemäß erstellt wurden. TestStateManagement.test_inode_create
schlägt fehl und gibt an, dass Null-Ergebnisse abgerufen wurden.
Dies sind die Artefakte, die durch die obigen Komponententests getestet werden.
%Vor%twisted.trial
Nach dem Prüfen mit sqlitebrowser scheint es, als ob die Daten in db.sqlite
geschrieben werden, so dass es aussieht wie ein Problem retrieval . Von hier aus bin ich irgendwie ratlos ... irgendwelche Ideen?
Dieser Code erzeugt ein inode
, das zum Testen verwendet werden kann.
Okay, es stellt sich heraus, dass dies ein bisschen schwierig ist. Das Ausführen der Tests isoliert (wie in dieser Frage geschrieben) macht es so, dass der Fehler nur selten auftritt. Wenn es jedoch im Kontext einer ganzen Testsuite ausgeführt wird, schlägt es fast zu 100% fehl.
Ich habe yield task.deferLater(reactor, .00001, lambda: None)
nach dem Schreiben in die db und vor dem Lesen aus der db hinzugefügt, und das löst das Problem.
Von da an vermutete ich, dass es sich um eine Race Condition handeln könnte, die aus dem Verbindungspool und der begrenzten Parallelitätstoleranz von SQLite herrührt. Ich habe versucht, die Parameter cb_min
und cb_max
auf ConnectionPool
auf 1
zu setzen, und das hat auch das Problem gelöst.
Kurz gesagt: es scheint, dass sqlite nicht sehr gut mit mehreren Verbindungen funktioniert, und dass die geeignete Lösung darin besteht, die Parallelität so weit wie möglich zu vermeiden.
Wenn Sie sich Ihre setUp
-Funktion ansehen, geben Sie self.db.runInteraction(...)
zurück, was eine verzögerte zurückgibt. Wie Sie bereits festgestellt haben, gehen Sie davon aus, dass das Warten darauf wartet, dass das Deferred beendet wird. Dies ist jedoch nicht der Fall und es ist eine Falle, der die meisten zum Opfer fallen (ich selbst eingeschlossen). Ich werde ehrlich zu Ihnen sein, für Situationen wie diese, insbesondere für Komponententests, führe ich einfach den synchronen Code außerhalb der TestCase
-Klasse aus, um die Datenbank zu initialisieren. Zum Beispiel:
Alternativ könnten Sie das Setup und yield runOperation(...)
dekorieren, aber etwas sagt mir, dass es nicht funktionieren würde ... Auf jeden Fall ist es überraschend, dass keine Fehler aufgetreten sind.
PS
Ich habe diese Frage schon eine Weile im Kopf gehabt und seit Tagen in meinem Hinterkopf. Ein möglicher Grund dafür dämmerte mir schließlich um 1 Uhr morgens. Aber ich bin zu müde / faul, um das auszuprobieren: D, aber es ist eine verdammt gute Ahnung. Ich möchte Sie auf dieser Detailebene in dieser Frage loben.
Tags und Links python unit-testing sqlite3 twisted python-db-api