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?
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)
J'étais fatigué d'avoir à ajouter manuellement le grand code de Fabio à tous les setUp
s, 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é.
Je suggère d'utiliser un LogCapture et de tester que vous enregistrez vraiment ce que vous attendez de vous:
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.
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.