Pythonアプリケーションの開発と単体テストにPyDevを使用しています。単体テストに関しては、ロギングフレームワークにコンテンツが記録されないという事実を除き、すべてが正常に動作します。 PyDevの「キャプチャされた出力」によってキャプチャされます。
次のように、標準出力に記録されたすべてをすでに転送しています。
import sys
logger = logging.getLogger()
logger.level = logging.DEBUG
logger.addHandler(logging.StreamHandler(sys.stdout))
それでも、「キャプチャされた出力」には、ロガーに記録されたものは表示されません。
Unittest-scriptの例を次に示します。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")
コンソール出力は次のとおりです。
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
ただし、テストのCAPTURED OUTPUTは次のとおりです。
======================== CAPTURED OUTPUT =========================
AA
このテストの実行中にlogging.Logger
に記録されるすべてをキャプチャする方法を知っている人はいますか?
問題は、テストの開始前にunittest
ランナーがsys.stdout
/sys.stderr
を置き換え、StreamHandler
が元のsys.stdout
にまだ書き込みをしていることです。
'current' sys.stdout
をハンドラーに割り当てると、ハンドラーが機能するはずです(以下のコードを参照)。
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")
ただし、テスト中にハンドラーを追加/削除することをお勧めします。
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)
私はすべてのsetUp
sに Fabioの素晴らしいコード を手動で追加することにうんざりしたので、_unittest.TestCase
_をいくつかの___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)
_
これで、テストケースはLoggedTestCase
、つまりclass TestCase(LoggedTestCase)
の代わりにclass TestCase(unittest.TestCase)
から単純に継承でき、完了です。または、___metaclass__
_行を追加して、テストまたはわずかに変更したlogger
でLogThisTestCase
を定義できます。
LogCaptureを使用して、実際にログを記録することを期待しているかをテストすることをお勧めします。
私もこの問題に出会いました。最終的にStreamHandlerをサブクラス化し、sys.stdoutを取得するプロパティでストリーム属性をオーバーライドしました。そのようにして、ハンドラーはunittest.TestCaseがsys.stdoutにスワップしたストリームを使用します。
class CapturableHandler(logging.StreamHandler):
@property
def stream(self):
return sys.stdout
@stream.setter
def stream(self, value):
pass
その後、次のようなテストを実行する前にロギングハンドラーをセットアップできます(これにより、カスタムハンドラーがルートロガーに追加されます)。
def setup_capturable_logging():
if not logging.getLogger().handlers:
logging.getLogger().addHandler(CapturableHandler())
私のように、別々のモジュールにテストがある場合、各ユニットテストモジュールのインポートの後に、テストを実行する前にロギングが設定されていることを確認する行を追加できます。
import logutil
logutil.setup_capturable_logging()
これは最もクリーンなアプローチではないかもしれませんが、それは非常に簡単で、私にとってはうまくいきました。
テスト、開発、および本番用に異なるイニシャライザーモジュールがある場合は、イニシャライザーで何かを無効にするかリダイレクトすることができます。
Local.py、test.py、production.pyはすべてcommon.yから継承しています
common.pyは、このスニペットを含むすべてのメイン設定を行います:
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,
},
}
次に、test.pyでこれを持っています:
console_logger = Common.LOGGING.get('handlers').get('console')
console_logger['class'] = 'logging.FileHandler
console_logger['filename'] = './unitest.log
これにより、コンソールハンドラーがFileHandlerに置き換えられ、引き続きログを取得することができますが、運用コードベースに触れる必要はありません。