web-dev-qa-db-ja.com

PyDevユニットテスト:「キャプチャされた出力」のlogging.Loggerに記録されたテキストをキャプチャする方法

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に記録されるすべてをキャプチャする方法を知っている人はいますか?

50
gecco

問題は、テストの開始前に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)
57
Fabio Zadrozny

私はすべてのsetUpsに 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___行を追加して、テストまたはわずかに変更したloggerLogThisTestCaseを定義できます。

18
Tobias Kienzler

LogCaptureを使用して、実際にログを記録することを期待しているかをテストすることをお勧めします。

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

8
Chris Withers

私もこの問題に出会いました。最終的に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()

これは最もクリーンなアプローチではないかもしれませんが、それは非常に簡単で、私にとってはうまくいきました。

1
Narotak

テスト、開発、および本番用に異なるイニシャライザーモジュールがある場合は、イニシャライザーで何かを無効にするかリダイレクトすることができます。

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に置き換えられ、引き続きログを取得することができますが、運用コードベースに触れる必要はありません。