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
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.
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
Utilisez pytest
!
pip install pytest-Django
pytest --nomigrations
au lieu de ./manage.py test
./manage.py test
coûte 2 min 11,86 spytest --nomigrations
coûte 2,18 sVous 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
utilisez ./ test manage.py --keepdb lorsqu'il n'y a pas de modifications dans les fichiers de migration
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.