web-dev-qa-db-fra.com

Comment exécuter la base de données de test de Django uniquement en mémoire?

Mes Django tests unitaires prennent beaucoup de temps à exécuter, donc je cherche des moyens d'accélérer cela. J'envisage d'installer un SSD , mais je sais que cela a aussi ses inconvénients. Bien sûr, il y a des choses que je pourrais faire avec mon code, mais je cherche un correctif structurel. Même l'exécution d'un seul test est lente car la base de données a besoin pour être reconstruit/sud migré à chaque fois. Voici donc mon idée ...

Étant donné que je sais que la base de données de test sera toujours assez petite, pourquoi ne puis-je pas simplement configurer le système pour toujours conserver la base de données de test entière dans la RAM? Ne touchez jamais du tout le disque. Comment configurer cela dans Django? Je préférerais continuer à utiliser MySQL puisque c'est ce que j'utilise en production, mais si SQLite 3 ou autre chose rend cela facile, j'irais dans ce sens.

SQLite ou MySQL ont-ils une option pour s'exécuter entièrement en mémoire? Il devrait être possible de configurer un disque RAM puis de configurer la base de données de test pour y stocker ses données, mais je ne sais pas comment dire Django/MySQL pour utiliser un répertoire de données différent pour une certaine base de données, d'autant plus qu'il continue d'être effacé et recréé à chaque exécution (je suis sur un Mac FWIW).

116
Leopd

Si vous définissez votre moteur de base de données sur sqlite3 lorsque vous exécutez vos tests, Django utilisera une base de données en mémoire .

J'utilise du code comme celui-ci dans mon settings.py pour définir le moteur sur sqlite lors de l'exécution de mes tests:

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

Ou en Django 1.2:

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

Et enfin dans Django 1.3 et 1.4:

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

(Le chemin complet vers le backend n'est pas strictement nécessaire avec Django 1.3, mais rend le paramètre forward compatible.)

Vous pouvez également ajouter la ligne suivante, au cas où vous auriez des problèmes avec les migrations Sud:

    SOUTH_TESTS_MIGRATE = False
155
Etienne

Je crée généralement un fichier de paramètres séparé pour les tests et je l'utilise dans la commande de test, par exemple.

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

Il présente deux avantages:

  1. Vous n'avez pas besoin de rechercher test ou un tel mot magique dans sys.argv, test_settings.py peut simplement être

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

    Ou vous pouvez l'ajuster davantage pour vos besoins, séparant proprement les paramètres de test des paramètres de production.

  2. Un autre avantage est que vous pouvez exécuter le test avec le moteur de base de données de production au lieu de sqlite3 en évitant les bogues subtils, donc lors du développement de l'utilisation

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

    et avant de valider le code, exécutez une fois

    python manage.py test myapp
    

    juste pour être sûr que tous les tests passent vraiment.

80
Anurag Uniyal

MySQL prend en charge un moteur de stockage appelé "MEMORY", que vous pouvez configurer dans votre configuration de base de données (settings.py) En tant que tel:

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

Notez que le moteur de stockage MEMORY ne prend pas en charge les colonnes blob/texte, donc si vous utilisez Django.db.models.TextField cela ne fonctionnera pas pour vous.

22
muudscope

Je ne peux pas répondre à votre question principale, mais il y a deux ou trois choses que vous pouvez faire pour accélérer les choses.

Tout d'abord, assurez-vous que votre base de données MySQL est configurée pour utiliser InnoDB. Ensuite, il peut utiliser des transactions pour restaurer l'état de la base de données avant chaque test, ce qui, selon mon expérience, a entraîné une accélération massive. Vous pouvez passer une commande init de base de données dans votre settings.py (syntaxe 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" } 
        }
    }

Deuxièmement, vous n'avez pas besoin d'exécuter les migrations Sud à chaque fois. Ensemble SOUTH_TESTS_MIGRATE = False dans votre settings.py et la base de données sera créée avec plain syncdb, ce qui sera beaucoup plus rapide que l'exécution de toutes les migrations historiques.

15
Daniel Roseman

Vous pouvez faire un double réglage:

  • utiliser des tables transactionnelles: l'état initial des appareils sera défini à l'aide de la restauration de la base de données après chaque TestCase.
  • mettez votre répertoire de données de base de données sur le disque virtuel: vous gagnerez beaucoup en ce qui concerne la création de base de données et l'exécution du test sera également plus rapide.

J'utilise les deux astuces et je suis assez content.

Comment le configurer pour MySQL sur 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

Attention, c'est juste pour les tests, après le redémarrage de votre base de données de la mémoire est perdue!

10
Potr Czachur

Une autre approche: avoir une autre instance de MySQL fonctionnant dans un tempfs qui utilise un RAM Disk. Instructions dans ce billet de blog: Accélérer MySQL pour test dans Django .

Avantages:

  • Vous utilisez exactement la même base de données que votre serveur de production utilise
  • pas besoin de changer votre configuration mysql par défaut
3
neves

En étendant la réponse d'Anurag, j'ai simplifié le processus en créant les mêmes paramètres test_settings et en ajoutant les éléments suivants à 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")

semble plus propre car sys est déjà importé et manage.py n'est utilisé que via la ligne de commande, donc pas besoin d'encombrer les paramètres

2
Alvin