web-dev-qa-db-fra.com

PyDev unittesting: comment capturer du texte enregistré dans une journalisation. Logger dans "Captured Output"

J'utilise PyDev pour le développement et les tests unitaires de mon Python. Quant aux tests unitaires, tout fonctionne très bien sauf le fait qu'aucun contenu n'est enregistré dans le cadre de journalisation. L'enregistreur n'est pas capturé par la "Sortie capturée" de PyDev.

Je transfère déjà tout ce qui est connecté à la sortie standard comme ceci:

import sys
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))

Néanmoins, la "Sortie capturée" n'affiche pas les informations enregistrées dans les enregistreurs.

Voici un exemple de script unittest: test.py

import sys
import unittest
import logging

logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))

class TestCase(unittest.TestCase):
    def testSimpleMsg(self):
        print("AA")
        logging.getLogger().info("BB")

La sortie de la console est:

Finding files... done.
Importing test modules ... done.

testSimpleMsg (itf.lowlevel.tests.hl7.TestCase) ... AA
2011-09-19 16:48:00,755 - root - INFO - BB
BB
ok

----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Mais le CAPTURED OUTPUT pour le test est:

======================== CAPTURED OUTPUT =========================
AA

Quelqu'un sait-il comment capturer tout ce qui est connecté à un logging.Logger pendant l'exécution de ce test?

50
gecco

Le problème est que le runner unittest remplace sys.stdout/sys.stderr avant le début des tests, et le StreamHandler écrit toujours sur le sys.stdout.

Si vous attribuez la valeur "actuelle" sys.stdout au gestionnaire, cela devrait fonctionner (voir le code ci-dessous).

import sys
import unittest
import logging

logger = logging.getLogger()
logger.level = logging.DEBUG
stream_handler = logging.StreamHandler(sys.stdout)
logger.addHandler(stream_handler)

class TestCase(unittest.TestCase):
    def testSimpleMsg(self):
        stream_handler.stream = sys.stdout
        print("AA")
        logging.getLogger().info("BB")

Cependant, une meilleure approche consisterait à ajouter/supprimer le gestionnaire pendant le test:

import sys
import unittest
import logging

logger = logging.getLogger()
logger.level = logging.DEBUG

class TestCase(unittest.TestCase):
    def testSimpleMsg(self):
        stream_handler = logging.StreamHandler(sys.stdout)
        logger.addHandler(stream_handler)
        try:
            print("AA")
            logging.getLogger().info("BB")
        finally:
            logger.removeHandler(stream_handler)
57
Fabio Zadrozny

J'étais fatigué d'avoir à ajouter manuellement le grand code de Fabio à tous les setUps, donc j'ai sous-classé unittest.TestCase Avec quelques __metaclass__ Ing:

class LoggedTestCase(unittest.TestCase):
    __metaclass__ = LogThisTestCase
    logger = logging.getLogger("unittestLogger")
    logger.setLevel(logging.DEBUG) # or whatever you prefer

class LogThisTestCase(type):
    def __new__(cls, name, bases, dct):
        # if the TestCase already provides setUp, wrap it
        if 'setUp' in dct:
            setUp = dct['setUp']
        else:
            setUp = lambda self: None
            print "creating setUp..."

        def wrappedSetUp(self):
            # for hdlr in self.logger.handlers:
            #    self.logger.removeHandler(hdlr)
            self.hdlr = logging.StreamHandler(sys.stdout)
            self.logger.addHandler(self.hdlr)
            setUp(self)
        dct['setUp'] = wrappedSetUp

        # same for tearDown
        if 'tearDown' in dct:
            tearDown = dct['tearDown']
        else:
            tearDown = lambda self: None

        def wrappedTearDown(self):
            tearDown(self)
            self.logger.removeHandler(self.hdlr)
        dct['tearDown'] = wrappedTearDown

        # return the class instance with the replaced setUp/tearDown
        return type.__new__(cls, name, bases, dct)

Maintenant, votre scénario de test peut simplement hériter de LoggedTestCase, c'est-à-dire class TestCase(LoggedTestCase) au lieu de class TestCase(unittest.TestCase) et vous avez terminé. Alternativement, vous pouvez ajouter la ligne __metaclass__ Et définir le logger dans le test ou un LogThisTestCase légèrement modifié.

18
Tobias Kienzler

Je suggère d'utiliser un LogCapture et de tester que vous enregistrez vraiment ce que vous attendez de vous:

http://testfixtures.readthedocs.org/en/latest/logging.html

8
Chris Withers

J'ai aussi rencontré ce problème. J'ai fini par sous-classer StreamHandler et à remplacer l'attribut stream par une propriété qui obtient sys.stdout. De cette façon, le gestionnaire utilisera le flux que le unittest.TestCase a échangé dans sys.stdout:

class CapturableHandler(logging.StreamHandler):

    @property
    def stream(self):
        return sys.stdout

    @stream.setter
    def stream(self, value):
        pass

Vous pouvez ensuite configurer le gestionnaire de journalisation avant d'exécuter des tests comme celui-ci (cela ajoutera le gestionnaire personnalisé à l'enregistreur racine):

def setup_capturable_logging():
    if not logging.getLogger().handlers:
        logging.getLogger().addHandler(CapturableHandler())

Si, comme moi, vous avez vos tests dans des modules séparés, vous pouvez simplement mettre une ligne après les importations de chaque module de test unitaire qui s'assurera que la journalisation est configurée avant l'exécution des tests:

import logutil

logutil.setup_capturable_logging()

Ce n'est peut-être pas l'approche la plus propre, mais c'est assez simple et cela a bien fonctionné pour moi.

1
Narotak

Si vous avez différents modules initaliser pour le test, le développement et la production, vous pouvez désactiver quoi que ce soit ou le rediriger dans l'initialiseur.

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,
        },
    }

Ensuite, 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 console par un FileHandler et signifie toujours obtenir la journalisation mais je n'ai pas à toucher la base du code de production.

0