Unit-Tests in Django

12

Ich kämpfe wirklich darum, effektive Komponententests für ein großes Django-Projekt zu schreiben. Ich habe eine einigermaßen gute Testabdeckung, habe jedoch festgestellt, dass es sich bei den von mir erstellten Tests definitiv um Integrations- / Akzeptanztests handelt, überhaupt nicht um Komponententests, und ich habe kritische Teile meiner Anwendung, die nicht effektiv getestet werden. Ich möchte dieses Problem so schnell wie möglich beheben.

Hier ist mein Problem. Mein Schema ist zutiefst relational und stark zeitorientiert, was meinem Modellobjekt eine hohe interne Kopplung und viel Zustand verleiht. Viele meiner Modellmethoden fragen basierend auf Zeitintervallen ab, und ich habe viel mit auto_now_addZeitstempeln zu tun. Nehmen Sie also eine Methode, die zum Beispiel so aussieht:

def summary(self, startTime=None, endTime=None):
    # ... logic to assign a proper start and end time 
    # if none was provided, probably using datetime.now()

    objects = self.related_model_set.manager_method.filter(...)

    return sum(object.key_method(startTime, endTime) for object in objects)

Wie geht man vor, um so etwas zu testen?

Hier bin ich soweit. Es fällt mir ein, dass das Unit-Test-Ziel ein falsches Verhalten by key_methodin Bezug auf seine Argumente zeigen sollte. summaryWird richtig gefiltert / aggregiert, um ein korrektes Ergebnis zu erzielen?

Datetime.now () zu verspotten ist einfach genug, aber wie kann ich den Rest des Verhaltens verspotten?

  • Ich könnte Fixtures verwenden, aber ich habe Vor- und Nachteile der Verwendung von Fixtures zum Erstellen meiner Daten gehört (schlechte Wartbarkeit ist für mich ein Nachteil).
  • Ich könnte meine Daten auch über den ORM einrichten, aber das kann einschränkend sein, da ich dann auch verwandte Objekte erstellen muss. Und mit dem ORM können Sie nicht auto_now_addmanuell mit Feldern herumspielen.
  • Das Verspotten des ORM ist eine weitere Option, aber es ist nicht nur schwierig, tief verschachtelte ORM-Methoden zu verspotten, sondern die Logik im ORM-Code wird aus dem Test herausverspottet, und das Verspotten scheint den Test wirklich von den Interna und Abhängigkeiten des zu abhängig zu machen zu prüfende Funktion.

Die am schwierigsten zu knackenden Nüsse scheinen Funktionen wie diese zu sein, die auf einigen Schichten von Modellen und Funktionen auf niedrigerer Ebene basieren und sehr zeitabhängig sind, auch wenn diese Funktionen möglicherweise nicht sehr kompliziert sind. Mein generelles Problem ist, dass meine Tests, egal wie ich sie aufschlitze, viel komplexer aussehen als die Funktionen, die sie testen.

Acjay
quelle
Sie sollten zuerst Komponententests schreiben, damit Sie Testbarkeitsprobleme in Ihrem Design erkennen können, bevor der eigentliche Produktionscode geschrieben wird.
Chedy2149
2
Das ist hilfreich, geht aber nicht wirklich auf die Frage ein, wie inhärent zustandsbehaftete, ORM-lastige Anwendungen am besten getestet werden können.
Sonntag,
Sie müssen die Persistenzschicht abstrahieren
Chedy2149
1
Klingt hypothetisch großartig, aber wenn es um die Projektwartung geht, ist das Einfügen einer maßgeschneiderten Persistenzschicht zwischen der Geschäftslogik und dem äußerst gut dokumentierten Django-ORM meines Erachtens nicht trivial. Plötzlich sind die Klassen voll mit winzigen Zwischenmethoden, die im Laufe der Zeit überarbeitet werden müssen. Aber vielleicht ist dies an Stellen gerechtfertigt, an denen die Testbarkeit von entscheidender Bedeutung ist.
Samstag,
Checkout this: vimeo.com/43612849 und this: vimeo.com/15007792
Chedy2149

Antworten:

6

Ich werde weitermachen und eine Antwort für das eintragen, was ich mir bisher ausgedacht habe.

Meine Hypothese ist, dass für eine Funktion mit tiefer Kopplung und Zustand die Realität ist, dass es einfach eine Menge Zeilen braucht, um den äußeren Kontext zu kontrollieren.

So sieht mein Testfall in etwa aus, wobei ich mich auf die Standard-Mock-Bibliothek stütze:

  1. Ich verwende das Standard-ORM, um die Reihenfolge der Ereignisse festzulegen
  2. Ich erstelle meinen eigenen Start datetimeund kehre die auto_now_addZeiten um, um sie an eine feste Zeitachse meines Designs anzupassen. Ich dachte, dass der ORM dies nicht zulässt, aber es funktioniert gut.
  3. Ich stelle sicher, dass die zu testende Funktion verwendet wird, from datetime import datetimedamit ich datetime.now()genau diese Funktion patchen kann (wenn ich die gesamte datetimeKlasse verspotte , ist der ORM fit).
  4. Ich erstelle meinen eigenen Ersatz für object.key_method(), mit einfacher, aber klar definierter Funktionalität, die von den Argumenten abhängt. Ich möchte, dass es von den Argumenten abhängt, da ich sonst möglicherweise nicht weiß, ob die Logik der zu testenden Funktion funktioniert. In meinem Fall wird einfach die Anzahl der Sekunden zwischen startTimeund zurückgegeben endTime. Ich baue es ein, indem ich es in ein Lambda einwickle und direkt object.key_method()mit dem new_callablekwarg von aufbaue patch.
  5. Schließlich führe ich eine Reihe von Asserts bei verschiedenen Aufrufen summarymit unterschiedlichen Argumenten durch, um die Gleichheit mit den erwarteten handkalkulierten Ergebnissen zu überprüfen, die das vorgegebene Verhalten des Mocks berücksichtigenkey_method

Dies ist natürlich wesentlich länger und komplizierter als die eigentliche Funktion. Es hängt von der DB ab und fühlt sich nicht wirklich wie ein Komponententest an. Es ist aber auch ziemlich von den Interna der Funktion entkoppelt - nur von ihrer Signatur und ihren Abhängigkeiten. Ich denke, es könnte immer noch ein Unit-Test sein.

In meiner App ist die Funktion sehr zentral und muss überarbeitet werden, um die Leistung zu optimieren. Ich denke, der Aufwand lohnt sich trotz der Komplexität. Aber ich bin immer noch offen für bessere Ideen, wie ich das angehen kann. Alles Teil meiner langen Reise in Richtung eines testgetriebenen Entwicklungsstils ...

Acjay
quelle