web-dev-qa-db-fra.com

Django Les tests unitaires prennent très longtemps pour créer la base de données de test

Depuis un certain temps maintenant, mes tests unitaires prennent plus de temps que prévu. J'ai essayé de le déboguer plusieurs fois sans grand succès, car les retards sont avant que mes tests ne commencent même à s'exécuter. Cela a affecté ma capacité à faire quoi que ce soit à distance proche du développement piloté par les tests (mes attentes sont peut-être trop élevées), donc je veux voir si je peux résoudre ce problème une fois pour toutes.

Lors de l'exécution d'un test, il existe un délai de 70 à 80 secondes entre le début et le début réel du test. Par exemple, si je lance un test pour un petit module (en utilisant time python manage.py test myapp), Je reçois

<... bunch of unimportant print messages I print from my settings>

Creating test database for alias 'default'...
......
----------------------------------------------------------------
Ran 6 tests in 2.161s

OK
Destroying test database for alias 'default'...

real    1m21.612s
user    1m17.170s
sys     0m1.400s

Environ 1m18 des 1m: 21 se situent entre le

Creating test database for alias 'default'...

et le

.......

ligne. En d'autres termes, le test prend moins de 3 secondes, mais l'initialisation de la base de données semble prendre 1: 18min

J'ai environ 30 applications, la plupart avec 1 à 3 modèles de base de données, ce qui devrait donner une idée de la taille du projet. J'utilise SQLite pour les tests unitaires et j'ai mis en œuvre certaines des améliorations suggérées. Je ne peux pas publier l'intégralité de mon fichier de paramètres, mais je suis heureux d'ajouter toutes les informations requises.

J'utilise un coureur

from Django.test.runner import DiscoverRunner
from Django.conf import settings

class ExcludeAppsTestSuiteRunner(DiscoverRunner):
    """Override the default Django 'test' command, exclude from testing
    apps which we know will fail."""

    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        if not test_labels:
            # No appnames specified on the command line, so we run all
            # tests, but remove those which we know are troublesome.
            test_labels = (
                'app1',
                'app2',
                ....
                )
            print ('Testing: ' + str(test_labels))

        return super(ExcludeAppsTestSuiteRunner, self).run_tests(
                test_labels, extra_tests, **kwargs)

et dans mes paramètres:

TEST_RUNNER = 'config.test_runner.ExcludeAppsTestSuiteRunner'

J'ai également essayé d'utiliser Django-nose avec Django-nose-exclude

J'ai lu beaucoup de choses sur la façon d'accélérer le test eux-mêmes, mais je n'ai trouvé aucune piste sur la façon d'optimiser ou d'éviter l'initialisation de la base de données. J'ai vu les suggestions d'essayer de ne pas tester avec la base de données mais je ne peux pas ou ne sais pas comment l'éviter complètement.

Veuillez me faire savoir si

  1. Ceci est normal et attendu
  2. Pas prévu (et j'espère un correctif ou une piste sur ce qu'il faut faire)

Encore une fois, je n'ai pas besoin d'aide pour accélérer le test eux-mêmes, mais l'initialisation (ou la surcharge). Je veux que l'exemple ci-dessus prenne 10 secondes au lieu de 80 secondes.

Merci beaucoup

Je lance le test (pour une seule application) avec --verbose 3 et a découvert que tout cela était lié aux migrations:

  Rendering model states... DONE (40.500s)
  Applying authentication.0001_initial... OK (0.005s)
  Applying account.0001_initial... OK (0.022s)
  Applying account.0002_email_max_length... OK (0.016s)
  Applying contenttypes.0001_initial... OK (0.024s)
  Applying contenttypes.0002_remove_content_type_name... OK (0.048s)
  Applying s3video.0001_initial... OK (0.021s)
  Applying s3picture.0001_initial... OK (0.052s)
  ... Many more like this

J'ai écrasé toutes mes migrations mais toujours lentement.

36
dkarchmer

La solution finale qui résout mon problème est de forcer Django pour désactiver la migration pendant les tests, ce qui peut être fait à partir des paramètres comme celui-ci

TESTING = 'test' in sys.argv[1:]
if TESTING:
    print('=========================')
    print('In TEST Mode - Disableling Migrations')
    print('=========================')

    class DisableMigrations(object):

        def __contains__(self, item):
            return True

        def __getitem__(self, item):
            return "notmigrations"

    MIGRATION_MODULES = DisableMigrations()

ou utilisez https://pypi.python.org/pypi/Django-test-without-migrations

Tout mon test prend maintenant environ 1 minute et une petite application prend 5 secondes.

Dans mon cas, les migrations ne sont pas nécessaires pour les tests car je mets à jour les tests lors de la migration et n'utilise pas les migrations pour ajouter des données. Cela ne fonctionnera pas pour tout le monde

28
dkarchmer

Sommaire

Utilisez pytest!

Les opérations

  1. pip install pytest-Django
  2. pytest --nomigrations au lieu de ./manage.py test

Résultat

  • ./manage.py test coûte 2 min 11,86 s
  • pytest --nomigrations coûte 2,18 s

Astuces

  • Vous pouvez créer un fichier appelé pytest.ini dans le répertoire racine de votre projet, et spécifiez options de ligne de commande par défaut et/ou paramètres Django .

    # content of pytest.ini
    [pytest]
    addopts = --nomigrations
    Django_SETTINGS_MODULE = yourproject.settings
    

    Maintenant, vous pouvez simplement exécuter des tests avec pytest et vous faire économiser un peu de frappe.

  • Vous pouvez accélérer encore les tests suivants en ajoutant --reuse-db aux options de ligne de commande par défaut.

    [pytest]
    addopts = --nomigrations --reuse-db
    

    Cependant, dès que votre modèle de base de données est modifié, vous devez exécuter pytest --create-db une fois à forcer la recréation de la base de données de test .

  • Si vous devez activer patch de gevent monkey pendant le test, vous pouvez créer un fichier appelé pytest dans le répertoire racine de votre projet avec le contenu suivant, y convertir le bit d'exécution (chmod +x pytest) et courir ./pytest pour tester au lieu de pytest:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # content of pytest
    from gevent import monkey
    
    monkey.patch_all()
    
    import os
    
    os.environ.setdefault("Django_SETTINGS_MODULE", "yourproject.settings")
    
    from Django.db import connection
    
    connection.allow_thread_sharing = True
    
    import re
    import sys
    
    from pytest import main
    
    if __== '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
        sys.exit(main())
    

    Vous pouvez créer un test_gevent.py fichier pour tester si la correction de gevent monkey est réussie:

    # -*- coding: utf-8 -*-
    # content of test_gevent.py
    import time
    from Django.test import TestCase
    from Django.db import connection
    import gevent
    
    
    def f(n):
        cur = connection.cursor()
        cur.execute("SELECT SLEEP(%s)", (n,))
        cur.execute("SELECT %s", (n,))
        cur.fetchall()
        connection.close()
    
    
    class GeventTestCase(TestCase):
        longMessage = True
    
        def test_gevent_spawn(self):
            timer = time.time()
            d1, d2, d3 = 1, 2, 3
            t1 = gevent.spawn(f, d1)
            t2 = gevent.spawn(f, d2)
            t3 = gevent.spawn(f, d3)
            gevent.joinall([t1, t2, t3])
            cost = time.time() - timer
            self.assertAlmostEqual(cost, max(d1, d2, d3), delta=1.0,
                                   msg='gevent spawn not working as expected')
    

Références

21
Rockallite

utilisez ./ test manage.py --keepdb lorsqu'il n'y a pas de modifications dans les fichiers de migration

14
Manoj

L'initialisation de la base de données prend en effet trop de temps ...

J'ai un projet avec environ le même nombre de modèles/tables (environ 77), et environ 350 tests et prend 1 minute au total pour tout exécuter. Plonger dans une machine vagabonde avec 2 cpus alloués et 2 Go de RAM. J'utilise également py.test avec le plugin pytest-xdist pour exécuter plusieurs tests en parallèle.

Une autre chose que vous pouvez faire est d'indiquer Django réutiliser la base de données de test et la recréer uniquement lorsque vous avez des modifications de schéma. Vous pouvez également utiliser SQLite pour que les tests utilisent une base de données en mémoire. Les deux approches sont expliquées ici: https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database

[~ # ~] modifier [~ # ~] : Si aucune des options ci-dessus ne fonctionne, une autre option consiste à faire hériter vos tests unitaires de Django SimpleTestCase ou utilisez un lanceur de test personnalisé qui ne crée pas de base de données comme expliqué dans cette réponse ici: tests unitaires Django sans db .

Ensuite, vous pouvez simplement vous moquer Django appels à la base de données en utilisant une bibliothèque comme celle-ci (ce que j'ai reconnu): https://github.com/stphivos/Django-mock- requêtes

De cette façon, vous pouvez exécuter vos tests unitaires localement rapidement et laisser votre serveur CI s'inquiéter d'exécuter des tests d'intégration qui nécessitent une base de données, avant de fusionner votre code dans une branche de développement/maître stable qui n'est pas celle de production.

5
fips