Come eseguire il database di test di Django solo in memoria?


125

I test delle mie unità Django richiedono molto tempo per essere eseguiti, quindi sto cercando dei modi per accelerarlo. Sto pensando di installare un SSD , ma so che ha anche i suoi lati negativi. Certo, ci sono cose che potrei fare con il mio codice, ma sto cercando una soluzione strutturale. Anche l'esecuzione di un singolo test è lenta poiché il database deve essere ricostruito / migrato a sud ogni volta. Quindi ecco la mia idea ...

Dal momento che so che il database di test sarà sempre piuttosto piccolo, perché non posso semplicemente configurare il sistema per mantenere sempre l'intero database di test nella RAM? Non toccare mai il disco. Come posso configurarlo in Django? Preferirei continuare a usare MySQL poiché è quello che uso in produzione, ma se SQLite  3 o qualcos'altro lo rendono semplice, farei così.

SQLite o MySQL hanno un'opzione per funzionare interamente in memoria? Dovrebbe essere possibile configurare un disco RAM e quindi configurare il database di prova per archiviare i suoi dati lì, ma non sono sicuro di come dire a Django / MySQL di utilizzare una directory di dati diversa per un determinato database, soprattutto perché continua a essere cancellato e ricreato ogni corsa. (Sono su un Mac FWIW.)

Risposte:


164

Se imposti il ​​tuo motore di database su sqlite3 quando esegui i test, Django utilizzerà un database in memoria .

Sto usando codice come questo nel mio settings.pyper impostare il motore su sqlite durante l'esecuzione dei miei test:

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

O in Django 1.2:

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

E infine in Django 1.3 e 1.4:

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

(Il percorso completo per il backend non è strettamente necessario con Django 1.3, ma rende compatibile l'impostazione di forward.)

È inoltre possibile aggiungere la seguente riga, nel caso in cui si riscontrino problemi con le migrazioni del sud:

    SOUTH_TESTS_MIGRATE = False

9
Si, esattamente. Avrei dovuto inserirlo nella mia risposta! Combina questo con SOUTH_TESTS_MIGRATE = False e i tuoi test dovrebbero essere molto più veloci.
Etienne,

7
questo è impressionante. nelle nuove configurazioni di django usa questa riga: 'ENGINE': 'sqlite3' se 'test' in sys.argv else 'django.db.backends.mysql',
mjallday

3
@Tomasz Zielinski - Hmm, dipende da cosa stai testando. Ma sono pienamente d'accordo sul fatto che, alla fine e di volta in volta, è necessario eseguire i test con il proprio database reale (Postgres, MySQL, Oracle ...). Ma eseguire i test in memoria con sqlite può farti risparmiare un sacco di tempo.
Etienne

3
Inverso da -1 a +1: come lo vedo ora, è molto più veloce usare sqlite per le corse veloci e passare a MySQL per esempio i test giornalieri finali. (Nota che ho dovuto fare una modifica fittizia per sbloccare il voto)
Tomasz Zieliński,

12
Attenzione con questo "test" in sys.argv; può innescarsi quando non lo si desidera, ad es manage.py collectstatic -i test. sys.argv[1] == "test"è una condizione più precisa che non dovrebbe avere questo problema.
keturn

83

Di solito creo un file di impostazioni separato per i test e lo uso nel comando test ad es

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

Ha due vantaggi:

  1. Non devi cercare testo cercare una parola simile in sys.argv, test_settings.pysemplicemente

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

    Oppure puoi modificarlo ulteriormente in base alle tue esigenze, separando in modo pulito le impostazioni di prova dalle impostazioni di produzione.

  2. Un altro vantaggio è che è possibile eseguire test con il motore di database di produzione anziché sqlite3 evitando bug sottili, quindi durante lo sviluppo dell'uso

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

    e prima di eseguire il codice eseguire una volta

    python manage.py test myapp

    solo per essere sicuri che tutti i test stiano davvero superando.


2
Mi piace questo approccio. Ho un sacco di file di impostazioni diverse e li uso per diversi ambienti server ma non avevo pensato di utilizzare questo metodo per scegliere un database di test diverso. Grazie per l'idea
Alexis Bellido,

Ciao Anurag, ho provato questo, ma anche i miei altri database menzionati nelle impostazioni sono eseguiti. Non sono in grado di capire il motivo esatto.
Bhupesh Pant,

Bella risposta. Mi chiedo come specificare un file di impostazioni quando si eseguono i test attraverso la copertura.
Wtower,

È un buon approccio, ma non ASCIUTTO. Django sa già che stai eseguendo dei test. Se potessi "agganciarti" a questa conoscenza in qualche modo, verrai impostato. Sfortunatamente, credo che ciò richieda l'estensione del comando di gestione. Probabilmente avrebbe senso rendere questo generico nel nucleo del framework, ad esempio avendo un'impostazione MANAGEMENT_COMMAND impostata sul comando corrente ogni volta che viene chiamato manage.py o qualcosa in tal senso.
DylanYoung,

2
@DylanYoung puoi asciugarlo includendo le impostazioni principali in test_settings e semplicemente ignorando le cose che desideri per il test.
Anurag Uniyal il

22

MySQL supporta un motore di archiviazione chiamato "MEMORY", che puoi configurare nel tuo database config ( settings.py) come tale:

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

Nota che il motore di archiviazione MEMORY non supporta le colonne BLOB / di testo, quindi se lo stai usando django.db.models.TextFieldnon funzionerà per te.


5
+1 per menzionare la mancanza di supporto per le colonne BLOB / di testo. Inoltre, non sembra supportare le transazioni ( dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html ).
Tuukka Mustonen,

Se vuoi davvero test in memoria, probabilmente stai meglio andando con sqlite che almeno supporta le transazioni.
atomic77,

15

Non posso rispondere alla tua domanda principale, ma ci sono un paio di cose che puoi fare per accelerare le cose.

Innanzitutto, assicurati che il tuo database MySQL sia impostato per utilizzare InnoDB. Quindi può utilizzare le transazioni per ripristinare lo stato del db prima di ogni test, il che nella mia esperienza ha portato a un enorme accelerazione. Puoi passare un comando init database nel tuo settings.py (sintassi Django 1.2):

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

In secondo luogo, non è necessario eseguire le migrazioni del sud ogni volta. Impostato SOUTH_TESTS_MIGRATE = Falsein settings.py e il database verrà creato con un semplice syncdb, che sarà molto più veloce rispetto a tutte le migrazioni storiche.


Ottimo consiglio! Ha ridotto i miei test da 369 tests in 498.704sa 369 tests in 41.334s . Questo è più di 10 volte più veloce!
Gabi Purcaru,

Esiste un interruttore equivalente in settings.py per le migrazioni in Django 1.7+?
Edward Newell,

@EdwardNewell Non esattamente. Tuttavia, è possibile utilizzare --keepper rendere persistente il database e non richiedere che il set completo di migrazioni venga riapplicato ad ogni esecuzione di test. Le nuove migrazioni verranno comunque eseguite. Se si passa frequentemente da una filiale all'altra, è comunque facile entrare in uno stato incoerente (è possibile ripristinare nuove migrazioni prima di passare modificando il database al database di test ed eseguendolo migrate, ma è un po 'una seccatura).
DylanYoung,

10

Puoi fare il doppio ritocco:

  • usa tabelle transazionali: lo stato iniziale dei dispositivi verrà impostato usando il rollback del database dopo ogni TestCase.
  • metti la tua directory dei dati del database su ramdisk: otterrai molto per quanto riguarda la creazione del database e anche l'esecuzione dei test sarà più veloce.

Sto usando entrambi i trucchi e sono abbastanza felice.

Come configurarlo per MySQL su Ubuntu:

$ 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

Attenzione, è solo per i test, dopo aver riavviato il database dalla memoria è perso!


Grazie! per me va bene. Non posso usare sqlite, perché sto usando funzionalità specifiche di mysql (indici full-text). Per gli utenti di Ubuntu, dovrai modificare la configurazione di Apparmor per consentire l'accesso mysqld a / dev / shm / mysql
Ivan Virabyan,

Saluti per l'heads up Ivan e Potr. Ho disabilitato il profilo mysql di AppArmor per ora, ma ho
trojjer

Hmm. Ho provato a personalizzare il profilo locale per fornire a mysqld l'accesso al percorso / dev / shm / mysql e al suo contenuto, ma il servizio può avviarsi solo in modalità "lamentarsi" (comando aa-lamentare) e non "imporre", per alcuni motivo ... Una domanda per un altro forum! Quello che non riesco a capire è come non ci siano "lamentele" quando funziona, il che implica che mysqld non sta violando il profilo ...
trojjer

4

Un altro approccio: avere un'altra istanza di MySQL in esecuzione in un tempfs che utilizza un disco RAM. Istruzioni in questo post del blog: velocizzare MySQL per i test in Django .

vantaggi:

  • Si utilizza esattamente lo stesso database utilizzato dal server di produzione
  • non è necessario modificare la configurazione mysql predefinita

2

Estendendo la risposta di Anurag ho semplificato il processo creando gli stessi test_settings e aggiungendo quanto segue a manage.py

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")

sembra più pulito poiché sys è già stato importato e manage.py viene utilizzato solo dalla riga di comando, quindi non è necessario ingombrare le impostazioni


2
Attenzione con questo "test" in sys.argv; può innescarsi quando non lo si desidera, ad es manage.py collectstatic -i test. sys.argv[1] == "test"è una condizione più precisa che non dovrebbe avere questo problema.
keturn

2
@keturn in questo modo genera un'eccezione quando si esegue ./manage.pysenza argomenti (ad es. per vedere quali plugin sono disponibili, lo stesso di --help)
Antony Hatchkins,

1
@AntonyHatchkins È banale da risolvere:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung il

1
@DylanYoung Sì, è esattamente quello che volevo che Alvin aggiungesse alla sua soluzione, ma non è particolarmente interessato a migliorarla. Comunque sembra più un trucco rapido che la soluzione legittima.
Antony Hatchkins,

1
non guardo questa risposta da un po ', ho aggiornato lo snippet per riflettere il miglioramento di @ DylanYoung
Alvin,

0

Usa sotto nel tuo setting.py

DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.