J'utilise un simple programme d'exécution de tests unitaires pour tester mon application Django.
Mon application elle-même est configurée pour utiliser un enregistreur de base dans settings.py en utilisant:
logging.basicConfig(level=logging.DEBUG)
Et dans mon code d'application en utilisant:
logger = logging.getLogger(__name__)
logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG))
Cependant, lors de l'exécution d'unittests, j'aimerais désactiver la journalisation afin que les résultats de test ne soient pas surchargés. Existe-t-il un moyen simple de désactiver la journalisation de manière globale, afin que les enregistreurs spécifiques à l'application n'écrivent pas de contenu dans la console lorsque j'effectue des tests?
logging.disable(logging.CRITICAL)
désactive tous les appels de journalisation dont le niveau est inférieur ou égal à CRITICAL
. La journalisation peut être réactivée avec
logging.disable(logging.NOTSET)
Puisque vous êtes dans Django, vous pouvez ajouter ces lignes à votre fichier settings.py:
import sys
import logging
if len(sys.argv) > 1 and sys.argv[1] == 'test':
logging.disable(logging.CRITICAL)
De cette façon, vous n'avez pas à ajouter cette ligne dans chaque setUp () de vos tests. :)
Vous pouvez également effectuer quelques modifications utiles pour répondre à vos besoins en matière de test.
Il existe un autre moyen "plus agréable" ou "plus propre" d'ajouter des détails à vos tests: créer votre propre programme de test.
Créez simplement une classe comme celle-ci:
import logging
from Django.test.simple import DjangoTestSuiteRunner
from Django.conf import settings
class MyOwnTestRunner(DjangoTestSuiteRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs):
# Don't show logging messages while testing
logging.disable(logging.CRITICAL)
return super(MyOwnTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)
Et ajoutez maintenant à votre fichier settings.py:
TEST_RUNNER = "PATH.TO.PYFILE.MyOwnTestRunner"
#(for example, 'utils.mytest_runner.MyOwnTestRunner')
Cela vous permet de faire une modification vraiment pratique que l’autre approche ne consiste pas à faire, Django teste simplement les applications souhaitées. Vous pouvez le faire en changeant les étiquettes de test_l ajoutant cette ligne au lanceur de test:
if not test_labels:
test_labels = ['my_app1', 'my_app2', ...]
J'aime l'idée de testeur personnalisée de Hassek. Notez que DjangoTestSuiteRunner
n'est plus le lanceur de tests par défaut de Django 1.6+, il a été remplacé par DiscoverRunner
. Pour le comportement par défaut, le lanceur de test devrait ressembler davantage à:
import logging
from Django.test.runner import DiscoverRunner
class NoLoggingTestRunner(DiscoverRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs):
# disable logging below CRITICAL while testing
logging.disable(logging.CRITICAL)
return super(NoLoggingTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)
Existe-t-il un moyen simple de désactiver la journalisation de manière globale afin que les enregistreurs spécifiques à l'application n'écrivent pas de contenu dans la console lors de l'exécution de tests?
Les autres réponses empêchent "d'écrire des choses sur la console" en configurant globalement l'infrastructure de journalisation pour qu'elle ignore tout. Cela fonctionne mais je trouve l'approche trop brutale. Mon approche consiste à effectuer un changement de configuration qui n'effectue que ce qui est nécessaire pour empêcher les journaux de sortir sur la console. J'ajoute donc un filtre de journalisation personnalisé à mon settings.py
:
from logging import Filter
class NotInTestingFilter(Filter):
def filter(self, record):
# Although I normally just put this class in the settings.py
# file, I have my reasons to load settings here. In many
# cases, you could skip the import and just read the setting
# from the local symbol space.
from Django.conf import settings
# TESTING_MODE is some settings variable that tells my code
# whether the code is running in a testing environment or
# not. Any test runner I use will load the Django code in a
# way that makes it True.
return not settings.TESTING_MODE
Et je configure la journalisation Django pour utiliser le filtre:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'testing': {
'()': NotInTestingFilter
}
},
'formatters': {
'verbose': {
'format': ('%(levelname)s %(asctime)s %(module)s '
'%(process)d %(thread)d %(message)s')
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'filters': ['testing'],
'formatter': 'verbose'
},
},
'loggers': {
'foo': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
}
}
Résultat final: lorsque je teste, rien ne va à la console, mais tout le reste reste le même.
Je conçois un code contenant des instructions de consignation qui ne sont déclenchées que dans des circonstances spécifiques et qui devraient générer les données exactes dont j'ai besoin pour le diagnostic en cas de problème. Par conséquent, je teste qu'ils font ce qu'ils sont censés faire et que, par conséquent, désactiver complètement la journalisation n'est pas viable pour moi. Une fois le logiciel en production, je ne veux pas savoir que ce que je pensais être consigné ne soit pas consigné.
De plus, certains lanceurs de tests (Nose, par exemple) captureront les journaux lors des tests et les afficheront avec un échec. C'est utile pour comprendre pourquoi un test a échoué. Si la journalisation est complètement désactivée, rien ne peut être capturé.
Il existe une méthode jolie et propre pour suspendre la connexion aux tests avec la méthode unittest.mock.patch
.
foo.py :
import logging
logger = logging.getLogger(__name__)
def bar():
logger.error('There is some error output here!')
return True
tests.py :
from unittest import mock, TestCase
from foo import bar
class FooBarTestCase(TestCase):
@mock.patch('foo.logger', mock.Mock())
def test_bar(self):
self.assertTrue(bar())
Et python3 -m unittest tests
ne produira aucune sortie de journalisation.
J'ai constaté que pour les tests dans une structure unittest
ou similaire, le moyen le plus efficace de désactiver en toute sécurité la journalisation non souhaitée dans les tests unitaires consiste à activer/désactiver les méthodes setUp
/tearDown
d'un scénario de test particulier. Cela permet à une cible spécifique de désactiver les journaux. Vous pouvez également le faire explicitement sur le consignateur de la classe que vous testez.
import unittest
import logging
class TestMyUnitTest(unittest.TestCase):
def setUp(self):
logging.disable(logging.CRITICAL)
def tearDown(self):
logging.disable(logging.NOTSET)
J'utilise un décorateur de méthode simple pour désactiver la journalisation uniquement dans une méthode de test particulière.
def disable_logging(f):
def wrapper(*args):
logging.disable(logging.CRITICAL)
result = f(*args)
logging.disable(logging.NOTSET)
return result
return wrapper
Et puis je l'utilise comme dans l'exemple suivant:
class ScenarioTestCase(TestCase):
@disable_logging
test_scenario(self):
pass
Parfois, vous voulez les journaux et parfois non. J'ai ce code dans mon settings.py
import sys
if '--no-logs' in sys.argv:
print('> Disabling logging levels of CRITICAL and below.')
sys.argv.remove('--no-logs')
logging.disable(logging.CRITICAL)
Ainsi, si vous exécutez votre test avec les options --no-logs
, vous obtiendrez uniquement les journaux critical
:
$ python ./manage.py tests --no-logs
> Disabling logging levels of CRITICAL and below.
C'est très utile si vous souhaitez accélérer les tests de votre flux d'intégration continue.
Si vous avez différents modules d'initialisation pour le test, le développement et la production, vous pouvez tout désactiver ou le rediriger dans l'initiale. J'ai local.py, test.py et production.py qui héritent tous de common.y
common.py fait toute la configuration principale, y compris cet extrait:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'Django.server': {
'()': 'Django.utils.log.ServerFormatter',
'format': '[%(server_time)s] %(message)s',
},
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'filters': {
'require_debug_true': {
'()': 'Django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'Django.server': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'Django.server',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'mail_admins': {
'level': 'ERROR',
'class': 'Django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'Django': {
'handlers': ['console'],
'level': 'INFO',
'propagate': True,
},
'celery.tasks': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
'Django.server': {
'handlers': ['Django.server'],
'level': 'INFO',
'propagate': False,
},
}
Puis dans test.py j'ai ceci:
console_logger = Common.LOGGING.get('handlers').get('console')
console_logger['class'] = 'logging.FileHandler
console_logger['filename'] = './unitest.log
Cela remplace le gestionnaire de la console par un FileHandler et signifie que la journalisation est toujours en cours, mais je n'ai pas à toucher à la base de code de production.
Si vous utilisez pytest
:
Puisque pytest capture les messages de journal et ne les affiche que pour les tests ayant échoué, vous ne souhaitez généralement pas désactiver la journalisation. A la place, utilisez un fichier settings.py
distinct pour les tests (par exemple, test_settings.py
) et ajoutez-y:
LOGGING_CONFIG = None
Ceci indique à Django de ne pas configurer la journalisation. Le paramètre LOGGING
sera ignoré et peut être supprimé des paramètres.
Avec cette approche, vous ne recevez pas de journalisation pour les tests réussis, et vous obtenez toute la journalisation disponible pour les tests ayant échoué.
Les tests seront exécutés à l'aide de la journalisation définie par pytest
. Il peut être configuré à votre convenance dans les paramètres pytest
(par exemple, tox.ini
). Pour inclure les messages de journal de niveau de débogage, utilisez log_level = DEBUG
(ou l'argument de ligne de commande correspondant).
Si vous ne le souhaitez pas, activez/désactivez-le à plusieurs reprises dans setUp () et tearDown () pour unittest (ne voyez pas la raison), vous pouvez le faire une fois par classe:
import unittest
import logging
class TestMyUnitTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
logging.disable(logging.CRITICAL)
@classmethod
def tearDownClass(cls):
logging.disable(logging.NOTSET)
Dans mon cas, j'ai un fichier de paramètres settings/test.py
créé spécifiquement à des fins de test.
from .base import *
DATABASES = {
'default': {
'ENGINE': 'Django.db.backends.sqlite3',
'NAME': 'test_db'
}
}
PASSWORD_HASHERS = (
'Django.contrib.auth.hashers.MD5PasswordHasher',
)
LOGGING = {}
Je mets une variable d'environnement Django_SETTINGS_MODULE=settings.test
à /etc/environment
.