Wie kann man die Testdatenbank von Django nur im Speicher ausführen?

125

Die Ausführung meiner Django-Komponententests dauert lange, daher suche ich nach Möglichkeiten, dies zu beschleunigen. Ich denke darüber nach, eine SSD zu installieren , aber ich weiß, dass dies auch Nachteile hat. Natürlich gibt es Dinge, die ich mit meinem Code tun könnte, aber ich suche nach einer strukturellen Lösung. Selbst das Ausführen eines einzelnen Tests ist langsam, da die Datenbank jedes Mal neu erstellt / nach Süden migriert werden muss. Also hier ist meine Idee ...

Da ich weiß, dass die Testdatenbank immer recht klein sein wird, warum kann ich das System nicht einfach so konfigurieren, dass immer die gesamte Testdatenbank im RAM bleibt? Berühren Sie niemals die Festplatte. Wie konfiguriere ich das in Django? Ich würde es vorziehen, MySQL weiter zu verwenden, da ich dies in der Produktion verwende, aber wenn SQLite  3 oder etwas anderes dies einfach macht, würde ich diesen Weg gehen.

Hat SQLite oder MySQL die Option, vollständig im Speicher ausgeführt zu werden? Es sollte möglich sein, eine RAM-Disk zu konfigurieren und dann die Testdatenbank so zu konfigurieren, dass ihre Daten dort gespeichert werden. Ich bin mir jedoch nicht sicher, wie ich Django / MySQL anweisen soll, ein anderes Datenverzeichnis für eine bestimmte Datenbank zu verwenden, zumal diese immer wieder gelöscht wird und jeden Lauf neu erstellt. (Ich bin auf einem Mac FWIW.)

Leopd
quelle

Antworten:

164

Wenn Sie Ihre Datenbank-Engine beim Ausführen Ihrer Tests auf sqlite3 setzen, verwendet Django eine speicherinterne Datenbank .

Ich verwende Code in meinem settings.py, um die Engine beim Ausführen meiner Tests auf SQLite zu setzen:

if 'test' in sys.argv:
    DATABASE_ENGINE = 'sqlite3'

Oder in Django 1.2:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'sqlite3'}

Und schließlich in Django 1.3 und 1.4:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

(Der vollständige Pfad zum Backend ist bei Django 1.3 nicht unbedingt erforderlich, macht die Vorwärtseinstellung jedoch kompatibel.)

Sie können auch die folgende Zeile hinzufügen, falls Sie Probleme mit Südmigrationen haben:

    SOUTH_TESTS_MIGRATE = False
Etienne
quelle
9
Ja genau. Ich hätte das in meine Antwort aufnehmen sollen! Kombinieren Sie das mit SOUTH_TESTS_MIGRATE = False und Ihre Tests sollten viel schneller sein.
Etienne
7
Das ist großartig. Verwenden Sie bei neueren Django-Setups diese Zeile: 'ENGINE': 'sqlite3', wenn 'test' in sys.argv, sonst 'django.db.backends.mysql',
mjallday
3
@Tomasz Zielinski - Hmm, es kommt darauf an, was Sie testen. Ich stimme jedoch voll und ganz zu, dass Sie am Ende und von Zeit zu Zeit die Tests mit Ihrer realen Datenbank (Postgres, MySQL, Oracle ...) ausführen müssen. Das Ausführen Ihrer Tests im Arbeitsspeicher mit SQLite kann Ihnen jedoch viel Zeit sparen.
Etienne
3
Ich kehre -1 zu +1 um: Wie ich es jetzt sehe, ist es viel schneller, SQLite für schnelle Läufe zu verwenden und für letzte tägliche Tests zu MySQL zu wechseln. (Beachten Sie, dass ich eine Dummy-Bearbeitung durchführen musste, um die Abstimmung freizuschalten)
Tomasz Zieliński
12
Vorsicht dabei "test" in sys.argv; es kann ausgelöst werden, wenn Sie es nicht möchten, z manage.py collectstatic -i test. sys.argv[1] == "test"ist eine genauere Bedingung, die dieses Problem nicht haben sollte.
Keturn
83

Normalerweise erstelle ich eine separate Einstellungsdatei für Tests und verwende sie im Testbefehl, z

python manage.py test --settings=mysite.test_settings myapp

Es hat zwei Vorteile:

  1. Sie müssen nicht nach testeinem solchen Zauberwort in sys.argv suchen, es test_settings.pykann einfach sein

    from settings import *
    
    # make tests faster
    SOUTH_TESTS_MIGRATE = False
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

    Oder Sie können es weiter an Ihre Bedürfnisse anpassen und die Testeinstellungen sauber von den Produktionseinstellungen trennen.

  2. Ein weiterer Vorteil ist, dass Sie Tests mit der Produktionsdatenbank-Engine anstelle von sqlite3 ausführen können, um subtile Fehler zu vermeiden

    python manage.py test --settings=mysite.test_settings myapp

    und vor dem Festschreiben von Code einmal ausführen

    python manage.py test myapp

    Nur um sicherzugehen, dass alle Tests wirklich bestanden werden.

Anurag Uniyal
quelle
2
Ich mag diesen Ansatz. Ich habe eine Reihe verschiedener Einstellungsdateien und verwende sie für verschiedene Serverumgebungen, aber ich hatte nicht darüber nachgedacht, mit dieser Methode eine andere Testdatenbank auszuwählen. Danke für die Idee.
Alexis Bellido
Hallo Anurag, ich habe es versucht, aber meine anderen in den Einstellungen genannten Datenbanken werden ebenfalls ausgeführt. Ich kann den genauen Grund nicht herausfinden.
Bhupesh Pant
Gute Antwort. Ich frage mich, wie man eine Einstellungsdatei angibt, wenn Tests durch Abdeckung ausgeführt werden.
Wtower
Es ist ein guter Ansatz, aber nicht trocken. Django weiß bereits, dass Sie Tests durchführen. Wenn Sie sich irgendwie in dieses Wissen einhaken könnten, wären Sie bereit. Leider glaube ich, dass dies die Erweiterung des Verwaltungsbefehls erfordert. Es wäre wahrscheinlich sinnvoll, dies generisch im Kern des Frameworks zu machen, indem beispielsweise bei jedem Aufruf von manage.py eine Einstellung MANAGEMENT_COMMAND auf den aktuellen Befehl gesetzt wird oder etwas in diesem Sinne.
DylanYoung
2
@DylanYoung Sie können es trocken machen, indem Sie die Haupteinstellungen in test_settings aufnehmen und nur die Dinge überschreiben, die Sie für den Test benötigen.
Anurag Uniyal
22

MySQL unterstützt eine Speicher-Engine namens "MEMORY", die Sie in Ihrer Datenbank config ( settings.py) als solche konfigurieren können :

    'USER': 'root',                      # Not used with sqlite3.
    'PASSWORD': '',                  # Not used with sqlite3.
    'OPTIONS': {
        "init_command": "SET storage_engine=MEMORY",
    }

Beachten Sie, dass die MEMORY-Speicher-Engine keine Blob- / Textspalten unterstützt. Wenn Sie django.db.models.TextFielddiese verwenden, funktioniert dies für Sie nicht.

Muudscope
quelle
5
+1 für die Erwähnung mangelnder Unterstützung für Blob- / Textspalten. Es scheint auch keine Transaktionen zu unterstützen ( dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html ).
Tuukka Mustonen
Wenn Sie wirklich In-Memory-Tests wünschen, ist es wahrscheinlich besser, sich für SQLite zu entscheiden, das zumindest Transaktionen unterstützt.
Atomic77
15

Ich kann Ihre Hauptfrage nicht beantworten, aber Sie können einige Dinge tun, um die Dinge zu beschleunigen.

Stellen Sie zunächst sicher, dass Ihre MySQL-Datenbank für die Verwendung von InnoDB eingerichtet ist. Dann kann es Transaktionen verwenden, um den Status der Datenbank vor jedem Test zurückzusetzen, was meiner Erfahrung nach zu einer massiven Beschleunigung geführt hat. Sie können einen Datenbank-Init-Befehl in Ihrer settings.py übergeben (Django 1.2-Syntax):

DATABASES = {
    'default': {
            'ENGINE':'django.db.backends.mysql',
            'HOST':'localhost',
            'NAME':'mydb',
            'USER':'whoever',
            'PASSWORD':'whatever',
            'OPTIONS':{"init_command": "SET storage_engine=INNODB" } 
        }
    }

Zweitens müssen Sie die Südmigrationen nicht jedes Mal ausführen. SOUTH_TESTS_MIGRATE = FalseWenn Sie in Ihrer settings.py festlegen, wird die Datenbank mit einer einfachen Synchronisierung erstellt, die viel schneller ist als das Durchlaufen aller historischen Migrationen.

Daniel Roseman
quelle
Toller Tipp! Es reduzierte meine Tests von 369 tests in 498.704sauf 369 tests in 41.334s . Das ist mehr als 10 mal schneller!
Gabi Purcaru
Gibt es in settings.py einen entsprechenden Schalter für Migrationen in Django 1.7+?
Edward Newell
@ EdwardNewell Nicht genau. Sie können --keepdie Datenbank jedoch beibehalten, ohne dass bei jedem Testlauf Ihr vollständiger Satz von Migrationen erneut angewendet werden muss. Neue Migrationen werden weiterhin ausgeführt. Wenn Sie häufig zwischen Zweigen wechseln, kann es leicht zu einem inkonsistenten Zustand kommen (Sie können neue Migrationen zurücksetzen, bevor Sie wechseln, indem Sie die Datenbank in die Testdatenbank ändern und ausführen migrate, dies ist jedoch etwas mühsam).
DylanYoung
10

Sie können doppelte Anpassungen vornehmen:

  • Verwenden Sie Transaktionstabellen: Der Status der anfänglichen Fixtures wird nach jedem TestCase mithilfe des Datenbank-Rollbacks festgelegt.
  • Legen Sie Ihr Datenbankdatenverzeichnis auf Ramdisk: Sie werden viel an Datenbankerstellung gewinnen und auch das Ausführen von Tests wird schneller sein.

Ich benutze beide Tricks und bin ziemlich glücklich.

So richten Sie es für MySQL unter Ubuntu ein:

$ sudo service mysql stop
$ sudo cp -pRL /var/lib/mysql /dev/shm/mysql

$ vim /etc/mysql/my.cnf
# datadir = /dev/shm/mysql
$ sudo service mysql start

Achtung, es dient nur zum Testen, nach dem Neustart geht Ihre Datenbank aus dem Speicher verloren!

Potr Czachur
quelle
Vielen Dank! funktioniert bei mir. Ich kann SQLite nicht verwenden, da ich Funktionen verwende, die für MySQL spezifisch sind (Volltextindizes). Für Ubuntu-Benutzer müssen Sie Ihre Apparmor-Konfiguration bearbeiten, um mysqld Zugriff auf / dev / shm / mysql
Ivan Virabyan
Prost auf die Heads-Ups Ivan und Potr. Das AppArmor- MySQL-
deaktiviert
Hmm. Ich habe versucht, das lokale Profil so anzupassen, dass mysqld Zugriff auf den Pfad / dev / shm / mysql und dessen Inhalt erhält, aber der Dienst kann für einige nur im Modus "Beschweren" (Befehl aa-Beschwerde) und nicht im Modus "Erzwingen" gestartet werden Grund ... Eine Frage für ein anderes Forum! Was ich nicht verstehen kann ist, dass es überhaupt keine "Beschwerden" gibt, wenn es funktioniert, was bedeutet, dass mysqld das Profil nicht verletzt ...
trojjer
4

Ein anderer Ansatz: Lassen Sie eine andere Instanz von MySQL in einem tempfs laufen, das eine RAM-Disk verwendet. Anweisungen in diesem Blog-Beitrag: Beschleunigen von MySQL zum Testen in Django .

Vorteile:

  • Sie verwenden genau dieselbe Datenbank, die Ihr Produktionsserver verwendet
  • Sie müssen Ihre Standard-MySQL-Konfiguration nicht ändern
neves
quelle
2

Als Erweiterung der Antwort von Anurag habe ich den Prozess vereinfacht, indem ich dieselben test_settings erstellt und der Datei manage.py Folgendes hinzugefügt habe

if len(sys.argv) > 1 and sys.argv[1] == "test":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.test_settings")
else:
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

scheint sauberer zu sein, da sys bereits importiert ist und manage.py nur über die Befehlszeile verwendet wird, sodass die Einstellungen nicht überladen werden müssen

Alvin
quelle
2
Vorsicht dabei "test" in sys.argv; es kann ausgelöst werden, wenn Sie es nicht möchten, z manage.py collectstatic -i test. sys.argv[1] == "test"ist eine genauere Bedingung, die dieses Problem nicht haben sollte.
Keturn
2
@keturn auf diese Weise generiert es eine Ausnahme, wenn es ./manage.pyohne Argumente ausgeführt wird (z. B. um zu sehen, welche Plugins verfügbar sind, wie --help)
Antony Hatchkins
1
@ AnthonyHatchkins Das ist trivial zu lösen:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung
1
@DylanYoung Ja, genau das wollte ich, dass Alvin seine Lösung ergänzt, aber er ist nicht besonders daran interessiert, sie zu verbessern. Auf jeden Fall sieht es eher nach einem schnellen Hack als nach einer legitimen Lösung aus.
Antony Hatchkins
1
Ich habe mir diese Antwort schon eine Weile nicht mehr angesehen und das Snippet aktualisiert, um die Verbesserung von @ DylanYoung widerzuspiegeln
Alvin
0

Verwenden Sie unten in Ihrem setting.py

DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
Ehsan Barkhordar
quelle