web-dev-qa-db-fra.com

Comment désactiver la journalisation lors de l'exécution de tests unitaires dans Python Django?

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?

138
shreddd
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)
211
unutbu

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', ...]
40
Hassek

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)
20
alukach

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.

Pourquoi faire ceci?

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é.

18
Louis

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.

2
valex

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)
2
mcguip

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
1
Eduard Mukans

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.

1
Karim N Gorjux

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.

0

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

0
Roger Dahl

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)
0
the pillow

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.

0
Dmitrii Mikhailov