Anwendung von Tests mit einem Unmanaged Model (feat. Table XXX doesn't exist)

Hatte ich bereits erwähnt, dass wir in der Firma daran arbeiten, den legacy Code, der mit Raw Query geschrieben wurde, auf eine Django ORM-basierte Architektur umzustellen?

Einige der Überlegungen, die ich letztes Mal aufgeschrieben habe, wurden bereits entschieden, und sowohl die Projektstruktur als auch die Methoden zur Dateiorganisation sind etwas klarer geworden. Jetzt möchte ich darüber posten, was passiert ist, während wir das Modell klassifiziert haben.

Während der Analyse der bestehenden SQL Query wurde überprüft, welche Tabellen mit anderen Tabellen verknüpft sind und wie groß der Einfluss ist. Die Klassifizierung der Modelle verlief bis dahin reibungslos. Ein Problem trat jedoch auf, als ich Testcodes schrieb, um zu überprüfen, ob alles gut funktioniert. Ein für Entwickler willkommener? Fehler trat auf:

Table my_database.XXX doesn't exist.

Was zur… warum gibt es diese Tabelle nicht?


Was ist ein Unmanaged Model?

In Django benutzt man das Modul django.db.models.Model, um ein Model zu definieren, das mit der DB verbunden ist. Da Django auf der Basis von Model Migrationen unterstützt, kann man bei der Entwicklung einer Django Native App den direkten Zugang von Django zur DB Table erlauben. Nähere Informationen zu migration werde ich das nächste Mal detaillierter posten.

In diesem Fall wird das Model, das in Django definiert ist, zum direkten Schema für die Datenbankverbindung.

Das standardmäßig in der Model-Klasse festgelegte managed = True ist die Einstellung für ein von Django verwaltetes Datenbankschema. Der tatsächliche Code sieht folgendermaßen aus:

class MyModel(models.Model):
  ...
  # Definieren der Datenbankfelder
  ...
  class Meta:
    db_table = "real_database_table_name"
    manage = True # Genau dieser Teil.

Die innere Meta Klasse im Model muss nicht explizit angegeben werden, wenn das Modell in einer Django Native App definiert wird, da Django Model dies standardmäßig festlegt.

Wenn die inside-the-Meta-Klasse konfigurierte managed-Variable auf False gesetzt ist, handelt es sich um ein Unmanaged Model, also ein Modell, das nicht von Django verwaltet wird.

Table xxx doesn’t exist

Endlich kann ich den Fehler erklären, mit dem ich konfrontiert war. Django Unit Test verwendet nicht die bestehende Datenbank, um Tests durchzuführen.

Es sollte auch nicht verwenden.

Selbst bei einer Entwicklungsdatenbank könnten andere Projekte beeinflusst werden oder die Datenbank könnte durch viele Testdurchführungen unordentlich werden (aufgrund vieler wiederholter Testfälle.. unschön).

Um dieses Problem zu lösen, erstellt der Django Test auf Grundlage der Modelle mit managed = True ein Test Database in der DB, erstellt temporäre Tabellen und führt darin die Tests durch. Nach Abschluss der Tests werden die Testdatenbank und die Tabellen gelöscht.

Dabei sind die in app/migration definierten Tabellenschemata von Bedeutung.

Obwohl gesagt wurde, dass über das Modell erstellt wird, muss, um das Modell tatsächlich in die DB anzuwenden, ein Zwischenstadium durchlaufen werden, um die Änderungen des Modells in die DB zu übernehmen. Daher verfolgt und bewertet Django die Änderungsspur über die migrations innerhalb jeder App.

+ Bei der Django-Einarbeitung wird häufig der Befehl python manage.py makemigrations, python manage.py migrate verwendet. Die Generierung erfolgt zu diesem Zeitpunkt.

Für ein Unmanaged Model sollte die Migration jedoch nicht verwendet werden. Django sollte nicht auf eine Datenbank zugreifen, die möglicherweise von anderen Diensten verwendet wird. Die DB gilt als Änderungsursprung, nicht Django. Daher sollte es managed = False sein.

Um Code von der DB in ein Django-Modell zu generieren, wird häufig der Befehl python manage.py inspectdb verwendet, was zu einem Modellcode führt, in dem managed = False zu finden ist.

Bei Tests ist die Situation jedoch ganz anders. Da es sich um eine temporäre DB handelt, spielt es keine Rolle, wenn sie generiert wird. Der TestDB kann jedoch aufgrund des Einflusses von managed = False nicht erstellt werden, was zu dem Fehler führt, dass die Tabelle nicht existiert.

Lösung, was soll man tun?

Mein Fall war einfach, wenn auch ein wenig klobig gelöst. (Es war letztlich erfolgreich, aber es ist nicht die empfohlene Methode und wird in Zukunft ersetzt.)

Mehrere Blogs schlagen vor, den Default Test Runner zu ändern, um den managed = False in der Meta Class des modells zu ändern, bevor der Test ausgeführt wird.

Das, was ich angewendet habe, und auch die unten aufgeführten Beispiele verwenden im Wesentlichen die oben beschriebene Methode.

Lösung #1: Abfangen des Befehls (Methode, die ich verwendet habe)

Um Tests durchzuführen, habe ich den Befehl abgefangen, wenn test enthalten war, so dass es managed = False geändert wird. Der eigentliche Code zur Änderung bestand aus nur zwei Zeilen und funktionierte wirklich gut.

Zuerst habe ich im Einstellungsfile Code definiert, der prüft, ob der Befehl test enthält.

Ich habe auf diesen Verweis verwiesen: https://stackoverflow.com/questions/53289057/how-to-run-django-test-when-managed-false

# settings.py
...
UNDER_TEST = (len(sys.argv) > 1 and sys.argv[1] == 'test')
...

Dann setzen Sie in der Metaklasse des Testmodells managed = getattr(settings, 'UNDER_TEST', False) wie unten gezeigt.

# models.py
from django.conf import settings # Modul, um die Werte von settings.py, die ich definiert habe, zu erhalten.

class MyModel(models.Model)
  ...
  # Definierte Felder
  ...
  class Meta(object):
      db_table = 'your_db_table'
      managed = getattr(settings, 'UNDER_TEST', False)

Lösung #2: Verwenden eines Customed Test Runner

Im oben beschriebenen Fall ist ein Vorteil, dass es einfach ist, Tests durchzuführen, aber der Nachteil ist, dass alle Modelle die managed Variablen auf denselben Wert ändern müssen.

Wenn dies fortgesetzt wird und es mehr Tabellen gibt, kann es sein, dass etwas übersehen wird, und wenn andere Teammitglieder Testcodes schreiben, müssen sie zu dieser Methode noch separate Kenntnis haben. (Viele Regeln machen den Entwickler das Leben schwer)

Wie bereits erwähnt, kann die Definition und Ausführung von Tests über einen separaten Test Runner eine konsistente, eigentliche Implementierung von Django beibehalten, und Teammitglieder können den Code ohne zusätzliche Übergabe reibungslos fortführen.

Die Idee ist, dass der Test Runner eine Klasse definiert, die bei der Erkennung von managed = False während des Tests alle Klassen in managed = True umwandelt und dann den Test durchführt.

Obwohl ich mit dieser Umsetzung noch keinen erfolgreichen Code habe, jederzeit einen Verweislink hierzu:

Wahrscheinlich lag das Problem daran, dass ich nach der erfolgreichen Methode oben den migration-Ordner spät gelöscht hatte. Der leere migration-Ordner war der Schuldige (er hätte vollständig entfernt werden sollen). Dies wird, wenn es in der Firma möglich ist, angewendet und aktualisiert.

Django 1.9 und höher

Django 1.8 und niedriger

Zum Abschied

Es war vielleicht nicht so ordentlich wie sonst, aber ich glaube, dass ich das Vertrauen gewinne, mich nach und nach zu ändern und anzupassen. Der eigentliche Beitrag mag nicht so lang sein, aber für diesen Blog hat es in der Firma viel Zeit in Anspruch genommen, um alles zu diskutieren… ㅠㅠ

Damit beende ich mein heutiges Posten, und beim nächsten Mal möchte ich mehr Details zu Django Test Runner oder migration, die ich heute während der Bug-Suche kennengelernt habe, posten! Vielen Dank fürs Lesen. Fragen oder Korrekturen sind jederzeit willkommen. Bitte verwenden Sie die Kommentarsection!