Django Unit Tests ohne DB


126

Gibt es eine Möglichkeit, Django-Unittests zu schreiben, ohne eine Datenbank einzurichten? Ich möchte die Geschäftslogik testen, für deren Einrichtung die Datenbank nicht erforderlich ist. Und obwohl es schnell ist, eine Datenbank einzurichten, brauche ich sie in manchen Situationen wirklich nicht.


Ich frage mich, ob das wirklich wichtig ist. Die Datenbank wird gespeichert + Wenn Sie keine Modelle haben, wird mit der Datenbank nichts ausgeführt. Wenn Sie es also nicht benötigen, richten Sie keine Modelle ein.
Torsten Engelbrecht

3
Ich habe Modelle, aber für diese Tests sind sie nicht relevant. Und die Datenbank wird nicht im Speicher gespeichert, sondern speziell für diesen Zweck in MySQL aufgebaut. Nicht, dass ich das will. Vielleicht könnte ich django so konfigurieren, dass eine speicherinterne Datenbank zum Testen verwendet wird. Wissen Sie, wie das geht?
Paweloque

Oh es tut mir leid. In-Memory-Datenbanken sind nur dann der Fall, wenn Sie eine SQLite-Datenbank verwenden. Abgesehen davon sehe ich keinen Weg, um das Erstellen der Test-Datenbank zu vermeiden. Es gibt nichts darüber in den Dokumenten + Ich hatte nie das Bedürfnis, es zu vermeiden.
Torsten Engelbrecht

3
Die akzeptierte Antwort hat mir nicht geholfen. Stattdessen funktionierte dies perfekt: caktusgroup.com/blog/2013/10/02/skipping-test-db-creation
Hugo Pineda

Antworten:


122

Sie können DjangoTestSuiteRunner in Unterklassen unterteilen und die zu übergebenden Methoden setup_databases und teardown_databases überschreiben.

Erstellen Sie eine neue Einstellungsdatei und setzen Sie TEST_RUNNER auf die neue Klasse, die Sie gerade erstellt haben. Wenn Sie dann Ihren Test ausführen, geben Sie Ihre neue Einstellungsdatei mit dem Flag --settings an.

Folgendes habe ich getan:

Erstellen Sie einen benutzerdefinierten Testanzugläufer, der dem folgenden ähnelt:

from django.test.simple import DjangoTestSuiteRunner

class NoDbTestRunner(DjangoTestSuiteRunner):
  """ A test runner to test without database creation """

  def setup_databases(self, **kwargs):
    """ Override the database creation defined in parent class """
    pass

  def teardown_databases(self, old_config, **kwargs):
    """ Override the database teardown defined in parent class """
    pass

Erstellen Sie benutzerdefinierte Einstellungen:

from mysite.settings import *

# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'

Wenn Sie Ihre Tests ausführen, führen Sie sie wie folgt aus, wobei das Flag --settings auf Ihre neue Einstellungsdatei gesetzt ist:

python manage.py test myapp --settings='no_db_settings'

UPDATE: April / 2018

Da Django 1.8, das Modul wurde bewegt zu .django.test.simple.DjangoTestSuiteRunner 'django.test.runner.DiscoverRunner'

Weitere Informationen finden Sie im offiziellen Dokumentabschnitt über benutzerdefinierte Testläufer.


2
Dieser Fehler tritt auf, wenn Sie Tests haben, für die Datenbanktransaktionen erforderlich sind. Wenn Sie keine Datenbank haben, können Sie diese Tests natürlich nicht ausführen. Sie sollten Ihre Tests separat ausführen. Wenn Sie Ihren Test nur mit python manage.py test --settings = new_settings.py ausführen, werden eine ganze Reihe anderer Tests von anderen Apps ausgeführt, für die möglicherweise eine Datenbank erforderlich ist.
Mohi666

5
Beachten Sie, dass Sie SimpleTestCase anstelle von TestCase für Ihre Testklassen erweitern müssen. TestCase erwartet eine Datenbank.
Ben Roberts

9
Wenn Sie keine neue Einstellungsdatei verwenden möchten, können Sie den neuen TestRunner in der Befehlszeile mit der --testrunnerOption angeben .
Bran Handley

26
Gute Antwort!! In django 1.8 wurde von django.test.simple import DjangoTestSuiteRunner in django.test.runner import DiscoverRunner geändert. Hoffnung, die jemandem hilft!
Josh Brown

2
In Django 1.8 und höher kann eine geringfügige Korrektur des obigen Codes vorgenommen werden. Die import-Anweisung kann geändert werden in: from django.test.runner import DiscoverRunner Der NoDbTestRunner muss jetzt die DiscoverRunner-Klasse erweitern.
Aditya Satyavada

77

Im Allgemeinen können Tests in einer Anwendung in zwei Kategorien eingeteilt werden

  1. Unit-Tests, diese testen die einzelnen Code-Schnipsel in Sonneneinstrahlung und müssen nicht in die Datenbank gehen
  2. Integrationstestfälle, die tatsächlich in die Datenbank gehen und die vollständig integrierte Logik testen.

Django unterstützt sowohl Unit- als auch Integrationstests.

Unit-Tests erfordern kein Einrichten und Herunterfahren der Datenbank. Diese sollten wir von SimpleTestCase erben .

from django.test import SimpleTestCase


class ExampleUnitTest(SimpleTestCase):
    def test_something_works(self):
        self.assertTrue(True)

Bei der Integration erben Testfälle, die von TestCase geerbt werden, wiederum von TransactionTestCase und es wird die Datenbank eingerichtet und heruntergefahren, bevor jeder Test ausgeführt wird.

from django.test import TestCase


class ExampleIntegrationTest(TestCase):
    def test_something_works(self):
        #do something with database
        self.assertTrue(True)

Diese Strategie stellt sicher, dass die Datenbank nur für die Testfälle erstellt und zerstört wird, die auf die Datenbank zugreifen, und daher die Tests effizienter sind


37
Dies kann das Ausführen von Tests effizienter machen. Beachten Sie jedoch, dass der Testläufer bei der Initialisierung weiterhin Testdatenbanken erstellt.
Mönch

6
So viel einfacher als die gewählte Antwort. Ich danke dir sehr!
KFunk

1
@monkut Nein ... wenn Sie nur die SimpleTestCase-Klasse haben, führt der Testläufer nichts aus, siehe dieses Projekt .
Claudio Santos

Django wird weiterhin versuchen, eine Test-DB zu erstellen, auch wenn Sie nur SimpleTestCase verwenden. Siehe diese Frage .
Marko Prcać

Die Verwendung von SimpleTestCase funktioniert genau zum Testen von Dienstprogrammmethoden oder Snippets und verwendet oder erstellt keine Testdatenbank. Genau das, was ich brauche!
Tyro Hunter

28

Von django.test.simple

  warnings.warn(
      "The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
      "use django.test.runner.DiscoverRunner instead.",
      RemovedInDjango18Warning)

Also überschreiben DiscoverRunnerstatt DjangoTestSuiteRunner.

 from django.test.runner import DiscoverRunner

 class NoDbTestRunner(DiscoverRunner):
   """ A test runner to test without database creation/deletion """

   def setup_databases(self, **kwargs):
     pass

   def teardown_databases(self, old_config, **kwargs):
     pass

Verwenden Sie so:

python manage.py test app --testrunner=app.filename.NoDbTestRunner

8

Ich habe mich entschieden, von django.test.runner.DiscoverRunnerder run_testsMethode zu erben und sie ein paar Mal zu ergänzen .

Mein erster Zusatz prüft, ob das Einrichten einer Datenbank erforderlich ist, und ermöglicht die Aktivierung der normalen setup_databasesFunktionalität, wenn eine Datenbank erforderlich ist. Mein zweiter Zusatz ermöglicht das teardown_databasesAusführen des Normalen , wenn die setup_databasesMethode ausgeführt werden durfte.

Mein Code geht davon aus, dass für jeden TestCase, der von django.test.TransactionTestCase(und damit django.test.TestCase) erbt , eine Datenbank eingerichtet werden muss. Ich habe diese Annahme gemacht, weil die Django-Dokumente sagen:

Wenn Sie eine der anderen komplexeren und schwereren Django-spezifischen Funktionen benötigen, wie ... Testen oder Verwenden des ORM ..., sollten Sie stattdessen TransactionTestCase oder TestCase verwenden.

https://docs.djangoproject.com/de/1.6/topics/testing/tools/#django.test.SimpleTestCase

mysite / scripts / settings.py

from django.test import TransactionTestCase     
from django.test.runner import DiscoverRunner


class MyDiscoverRunner(DiscoverRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        """
        Run the unit tests for all the test labels in the provided list.

        Test labels should be dotted Python paths to test modules, test
        classes, or test methods.

        A list of 'extra' tests may also be provided; these tests
        will be added to the test suite.

        If any of the tests in the test suite inherit from
        ``django.test.TransactionTestCase``, databases will be setup. 
        Otherwise, databases will not be set up.

        Returns the number of tests that failed.
        """
        self.setup_test_environment()
        suite = self.build_suite(test_labels, extra_tests)
        # ----------------- First Addition --------------
        need_databases = any(isinstance(test_case, TransactionTestCase) 
                             for test_case in suite)
        old_config = None
        if need_databases:
        # --------------- End First Addition ------------
            old_config = self.setup_databases()
        result = self.run_suite(suite)
        # ----------------- Second Addition -------------
        if need_databases:
        # --------------- End Second Addition -----------
            self.teardown_databases(old_config)
        self.teardown_test_environment()
        return self.suite_result(suite, result)

Schließlich habe ich die folgende Zeile zur Datei settings.py meines Projekts hinzugefügt.

mysite / settings.py

TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'

Wenn ich jetzt nur nicht db-abhängige Tests ausführe, läuft meine Testsuite um eine Größenordnung schneller! :) :)


6

Aktualisiert: Siehe auch diese Antwort für die Verwendung eines Tools eines Drittanbieters pytest.


@Cesar ist richtig. Nach versehentlichem Ausführen ./manage.py test --settings=no_db_settingsohne Angabe eines App-Namens wurde meine Entwicklungsdatenbank gelöscht.

Verwenden Sie zur sichereren Verwendung dasselbe NoDbTestRunner, jedoch in Verbindung mit den folgenden mysite/no_db_settings.py:

from mysite.settings import *

# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'

# Use an alternative database as a safeguard against accidents
DATABASES['default']['NAME'] = '_test_mysite_db'

Sie müssen eine Datenbank erstellen, die _test_mysite_dbmit einem externen Datenbank-Tool aufgerufen wird . Führen Sie dann den folgenden Befehl aus, um die entsprechenden Tabellen zu erstellen:

./manage.py syncdb --settings=mysite.no_db_settings

Wenn Sie South verwenden, führen Sie auch den folgenden Befehl aus:

./manage.py migrate --settings=mysite.no_db_settings

OK!

Sie können jetzt Unit-Tests blitzschnell (und sicher) durchführen, indem Sie:

./manage.py test myapp --settings=mysite.no_db_settings

Ich habe Tests mit pytest (mit dem pytest-django-Plugin) und NoDbTestRunner durchgeführt. Wenn Sie versehentlich ein Objekt in einem Testfall erstellen und den Datenbanknamen nicht überschreiben, wird das Objekt in Ihren lokalen Datenbanken erstellt, die Sie in der Datenbank eingerichtet haben die Einstellungen. Der Name 'NoDbTestRunner' sollte 'NoTestDbTestRunner' sein, da die Testdatenbank nicht erstellt wird, sondern Ihre Datenbank aus den Einstellungen verwendet wird.
Gabriel Muj

2

Als Alternative zum Ändern Ihrer Einstellungen, um NoDbTestRunner "sicher" zu machen, finden Sie hier eine modifizierte Version von NoDbTestRunner, mit der die aktuelle Datenbankverbindung geschlossen und die Verbindungsinformationen aus den Einstellungen und dem Verbindungsobjekt entfernt werden. Funktioniert für mich, testen Sie es in Ihrer Umgebung, bevor Sie sich darauf verlassen :)

class NoDbTestRunner(DjangoTestSuiteRunner):
    """ A test runner to test without database creation """

    def __init__(self, *args, **kwargs):
        # hide/disconnect databases to prevent tests that 
        # *do* require a database which accidentally get 
        # run from altering your data
        from django.db import connections
        from django.conf import settings
        connections.databases = settings.DATABASES = {}
        connections._connections['default'].close()
        del connections._connections['default']
        super(NoDbTestRunner,self).__init__(*args,**kwargs)

    def setup_databases(self, **kwargs):
        """ Override the database creation defined in parent class """
        pass

    def teardown_databases(self, old_config, **kwargs):
        """ Override the database teardown defined in parent class """
        pass

HINWEIS: Wenn Sie die Standardverbindung aus der Verbindungsliste löschen, können Sie keine Django-Modelle oder andere Funktionen verwenden, die normalerweise die Datenbank verwenden (offensichtlich kommunizieren wir nicht mit der Datenbank, aber Django überprüft verschiedene von der Datenbank unterstützte Funktionen). . Es scheint auch, dass Connections._connections nicht mehr unterstützt wird __getitem__. Verwenden Sie diese Option, connections._connections.defaultum auf das Objekt zuzugreifen.
the_drow

2

Eine andere Lösung wäre, Ihre Testklasse einfach unittest.TestCasevon einer der Testklassen von Django erben zu lassen . Die Django-Dokumente ( https://docs.djangoproject.com/de/2.0/topics/testing/overview/#writing-tests ) enthalten die folgende Warnung:

Durch die Verwendung von unittest.TestCase werden die Kosten für die Ausführung jedes Tests in einer Transaktion und das Leeren der Datenbank vermieden. Wenn Ihre Tests jedoch mit der Datenbank interagieren, hängt ihr Verhalten von der Reihenfolge ab, in der der Testläufer sie ausführt. Dies kann zu Komponententests führen, die bei isolierter Ausführung bestanden werden, bei Ausführung in einer Suite jedoch fehlschlagen.

Wenn Ihr Test die Datenbank jedoch nicht verwendet, muss Sie diese Warnung nicht betreffen, und Sie können die Vorteile nutzen, wenn Sie nicht jeden Testfall in einer Transaktion ausführen müssen.


Es sieht so aus, als würde die Datenbank immer noch erstellt und zerstört. Der einzige Unterschied besteht darin, dass der Test in einer Transaktion nicht ausgeführt und die Datenbank nicht geleert wird.
Cam Rail

0

Die obigen Lösungen sind auch in Ordnung. Die folgende Lösung reduziert jedoch auch die Erstellungszeit der Datenbank, wenn mehr Migrationen vorhanden sind. Während des Komponententests wird die Ausführung von syncdb anstelle aller Südmigrationen viel schneller sein.

SOUTH_TESTS_MIGRATE = False # Zum Deaktivieren von Migrationen und zur Verwendung von syncdb


0

Mein Webhost erlaubt nur das Erstellen und Löschen von Datenbanken über die Web-GUI. Daher wurde beim Ausführen der Fehlermeldung "Fehler beim Erstellen der Testdatenbank: Berechtigung verweigert" angezeigt python manage.py test.

Ich hatte gehofft, die Option --keepdb für django-admin.py verwenden zu können, aber sie scheint ab Django 1.7 nicht mehr unterstützt zu werden.

Am Ende habe ich den Django-Code in ... / django / db / backends / created.py geändert, insbesondere die Funktionen _create_test_db und _destroy_test_db.

Denn _create_test_dbich habe die cursor.execute("CREATE DATABASE ...Zeile auskommentiert und durch ersetzt, passdamit der tryBlock nicht leer ist.

Denn _destroy_test_dbich habe es gerade auskommentiert cursor.execute("DROP DATABASE- ich musste es nicht durch irgendetwas ersetzen, da es bereits einen anderen Befehl im block ( time.sleep(1)) gab.

Danach liefen meine Tests einwandfrei - obwohl ich eine test_-Version meiner regulären Datenbank separat eingerichtet habe.

Dies ist natürlich keine großartige Lösung, da es bei einem Upgrade von Django nicht funktioniert, aber ich hatte aufgrund der Verwendung von virtualenv eine lokale Kopie von Django, sodass ich zumindest die Kontrolle darüber habe, wann / ob ich auf eine neuere Version aktualisiere.


0

Eine andere Lösung, die nicht erwähnt wurde: Dies war für mich einfach zu implementieren, da ich bereits mehrere Einstellungsdateien (für lokal / Staging / Produktion) habe, die von base.py erben. Im Gegensatz zu anderen Personen musste ich DATABASES ['default'] nicht überschreiben, da DATABASES nicht in base.py festgelegt ist

SimpleTestCase hat immer noch versucht, eine Verbindung zu meiner Testdatenbank herzustellen und Migrationen auszuführen. Wenn ich eine Datei config / settings / test.py erstellt habe, in der DATABASES nicht festgelegt wurde, wurden meine Komponententests ohne diese Datei ausgeführt. Es erlaubte mir, Modelle zu verwenden, die Fremdschlüssel und eindeutige Einschränkungsfelder hatten. (Die umgekehrte Suche nach Fremdschlüsseln, für die eine Datenbank-Suche erforderlich ist, schlägt fehl.)

(Django 2.0.6)

PS-Code-Schnipsel

PROJECT_ROOT_DIR/config/settings/test.py:
from .base import *
#other test settings

#DATABASES = {
# 'default': {
#   'ENGINE': 'django.db.backends.sqlite3',
#   'NAME': 'PROJECT_ROOT_DIR/db.sqlite3',
# }
#}

cli, run from PROJECT_ROOT_DIR:
./manage.py test path.to.app.test --settings config.settings.test

path/to/app/test.py:
from django.test import SimpleTestCase
from .models import *
#^assume models.py imports User and defines Classified and UpgradePrice

class TestCaseWorkingTest(SimpleTestCase):
  def test_case_working(self):
    self.assertTrue(True)
  def test_models_ok(self):
    obj = UpgradePrice(title='test',price=1.00)
    self.assertEqual(obj.title,'test')
  def test_more_complex_model(self):
    user = User(username='testuser',email='hi@hey.com')
    self.assertEqual(user.username,'testuser')
  def test_foreign_key(self):
    user = User(username='testuser',email='hi@hey.com')
    ad = Classified(user=user,headline='headline',body='body')
    self.assertEqual(ad.user.username,'testuser')
  #fails with error:
  def test_reverse_foreign_key(self):
    user = User(username='testuser',email='hi@hey.com')
    ad = Classified(user=user,headline='headline',body='body')
    print(user.classified_set.first())
    self.assertTrue(True) #throws exception and never gets here

0

Wenn Sie den Nasentestläufer (Django-Nase) verwenden, können Sie Folgendes tun:

my_project/lib/nodb_test_runner.py::

from django_nose import NoseTestSuiteRunner


class NoDbTestRunner(NoseTestSuiteRunner):
    """
    A test runner to test without database creation/deletion
    Used for integration tests
    """
    def setup_databases(self, **kwargs):
        pass

    def teardown_databases(self, old_config, **kwargs):
        pass

In Ihrem können settings.pySie dort den Testläufer angeben, dh

TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'

ODER

Ich wollte es nur zum Ausführen bestimmter Tests, also führe ich es so aus:

python manage.py test integration_tests/integration_*  --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.