Python Djangoで単体テストを実行している間、ロギングを無効にするにはどうすればよいですか?


168

私はDjangoアプリケーションをテストするために、単純な単体テストベースのテストランナーを使用しています。

私のアプリケーション自体はsettings.pyで基本的なロガーを使用するように設定されています:

logging.basicConfig(level=logging.DEBUG)

そして私のアプリケーションコードでは次を使用します:

logger = logging.getLogger(__name__)
logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG))

ただし、単体テストを実行するときは、ログを無効にして、テスト結果の出力が乱れないようにしたいと思います。ログをグローバルにオフにする簡単な方法はありますか?テストを実行すると、アプリケーション固有のロガーがコンソールにデータを書き出さないようにしますか?


テストの実行中にロギングをどのように有効にしましたか?そして、なぜdjango LOGGINGを使用しないのですか?
ダロア

回答:


249
logging.disable(logging.CRITICAL)

は、レベルがと同等かそれ以下のロギングコールをすべて無効にしますCRITICAL。ロギングは、

logging.disable(logging.NOTSET)

42
これは自明かもしれませんが、他の読者の利益のために自明であることを時々述べることは役立つと思います:(logging.disable受け入れられた回答から)への呼び出しをtests.py、ロギングを実行しているアプリケーションの上部に置きます。
CJガコネット

7
最終的にはsetUp()に呼び出しを入れましたが、あなたの主張はよく理解されています。
shreddd 2011

テストのsetUp()メソッド、または非表示にするログメッセージを生成する実際のテスト。
qris

10
そしてあなたのtearDown()方法では:logging.disable(logging.NOTSET)ロギングをきちんと元の場所に戻します。
mlissner 2013年

34
モジュールのinit .pyに置くとtests非常に便利です。
toabi 2013年

46

Djangoを使用しているので、これらの行をsettings.pyに追加できます。

import sys
import logging

if len(sys.argv) > 1 and sys.argv[1] == 'test':
    logging.disable(logging.CRITICAL)

そうすればsetUp()、テストのすべての行にその行を追加する必要がなくなります。

この方法で、テストのニーズに合わせていくつかの便利な変更を行うこともできます。

テストに詳細を追加するためのもう1つの「より良い」または「よりクリーンな」方法があり、それが独自のテストランナーになります。

次のようなクラスを作成するだけです。

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)

そして、settings.pyファイルに追加します。

TEST_RUNNER = "PATH.TO.PYFILE.MyOwnTestRunner"
#(for example, 'utils.mytest_runner.MyOwnTestRunner')

これにより、他のアプローチではできない、非常に便利な変更を1つ行うことができます。つまり、Djangoに必要なアプリケーションをテストさせるだけです。これを行うにはtest_labels、次の行をテストランナーに追加します。

if not test_labels:
    test_labels = ['my_app1', 'my_app2', ...]

もちろん、settings.pyに入れるとグローバルになります。
シュレッド

7
Django 1.6以降の場合は、@ alukachの回答を確認してください。
Hassek 2014

2
単体テストで、エラーがログに記録されたと主張したいことがあるので、この方法は理想的ではありません。それでも、それ良い答えです。
サルダトリオン

23

ログをグローバルにオフにする簡単な方法はありますか?テストを実行すると、アプリケーション固有のロガーがコンソールにデータを書き出さないようにしますか?

他の答えは、ログインフラストラクチャをグローバルに設定して何も無視することにより、「コンソールにデータを書き出す」ことを防ぎます。これは機能しますが、あまりにも鈍いアプローチだと思います。私のアプローチは、ログがコンソールに表示されないようにするために必要なことだけを行う構成変更を実行することです。だから私は私のにカスタムロギングフィルターを追加します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

そして、フィルターを使用するようにDjangoロギング構成します。

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

最終結果:私がテストしているとき、コンソールには何も行きませんが、他のすべては同じままです。

なぜこれを行うのですか?

特定の状況でのみトリガーされ、問題が発生した場合に診断に必要な正確なデータを出力するロギング命令を含むコードを設計します。したがって、私は彼らが彼らがすることになっていることをすることをテストします、そしてそれ故にロギングを完全に無効にすることは私にとって実行可能ではありません。ソフトウェアが本番環境に入ると、ログに記録されると思っていたものがログに記録されないことを知りたくありません。

さらに、一部のテストランナー(たとえば、Nose)は、テスト中にログをキャプチャし、テストの失敗と共にログの関連部分を出力します。テストが失敗した理由を解明するのに役立ちます。ロギングが完全にオフになっている場合、キャプチャできるものはありません。


「私が使用するどのテストランナーも、DjangoコードをTrueにする方法でロードします。」興味深い...どうやって?
webtweakers 2016年

test_settings.pyプロジェクトのの隣にあるファイルがありますsettings.py。これは、に設定されているように、ロードsettings.pyしていくつかの変更を行うように設定されTESTING_MODEていTrueます。私のテストランナーはtest_settings、Djangoプロジェクト設定用に読み込まれたモジュールになるように編成されています。これを行うには多くの方法があります。通常、環境変数DJANGO_SETTINGS_MODULEをに設定しますproj.test_settings
Louis

これは素晴らしく、まさに私が望むことをします。ユニットテスト中、何かが失敗するまでログを非表示にします。DjangoNoseは出力を取得し、失敗とともに出力します。完璧です。それを組み合わせる このユニットテストがアクティブであるかどうかを決定します。
rrauenza

21

Hassekのカスタムテストランナーのアイデアが好きです。それは注意すべきではないDjangoTestSuiteRunner、もはやジャンゴ1.6+のデフォルトのテストランナーである、それは置き換えられていますDiscoverRunner。デフォルトの動作では、テストランナーは次のようになります。

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)

多くのことを試した後、私はあなたの解決策を見つけました。ただし、test_runnerファイルがあるモジュールをインポートできないため、変数TEST_RUNNERを設定で設定できません。
バニーウサギ、

インポートの問題のように聞こえます。TEST_RUNNERを(実際のPythonモジュールではなく)ランナーへの文字列パスに設定していますか?また、ランナーはどこにありますか?私はという別のアプリhelpersを使っています。このアプリには、プロジェクト内の他のどこからもインポートされないユーティリティしかありません。
alukach

5

unittestフレームワーク内または類似のテストの場合、単体テストでの不要なロギングを安全に無効にする最も効果的な方法は、特定のテストケースのsetUp/ tearDownメソッドで有効/無効にすることです。これにより、ログを無効にする必要がある特定のターゲットを指定できます。テストするクラスのロガーでこれを明示的に行うこともできます。

import unittest
import logging

class TestMyUnitTest(unittest.TestCase):
    def setUp(self):
        logging.disable(logging.CRITICAL)

    def tearDown(self):
        logging.disable(logging.NOTSET)

4

単純なメソッドデコレータを使用して、特定のテストメソッドでのみログを無効にしています。

def disable_logging(f):

    def wrapper(*args):
        logging.disable(logging.CRITICAL)
        result = f(*args)
        logging.disable(logging.NOTSET)

        return result

    return wrapper

次に、次の例のように使用します。

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass

3

メソッドを使用したテストでロギングを一時停止するための、いくつかのきれいな方法があり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())

またpython3 -m unittest tests、ログ出力は生成されません。


1

ログが必要な場合と不要な場合があります。私はこのコードを持っています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)

したがって、--no-logsオプションを指定してテストを実行すると、criticalログのみが取得されます。

$ python ./manage.py tests --no-logs
> Disabling logging levels of CRITICAL and below.

継続的インテグレーションフローでテストを高速化する場合に非常に役立ちます。


1

ユニットテストのためにsetUp()とtearDown()で繰り返しオン/オフしたくない場合(その理由は表示されません)、クラスごとに1回だけ実行できます。

    import unittest
    import logging

    class TestMyUnitTest(unittest.TestCase):
        @classmethod
        def setUpClass(cls):
            logging.disable(logging.CRITICAL)
        @classmethod
        def tearDownClass(cls):
            logging.disable(logging.NOTSET)

1

特定のロガーを一時的に抑制したい場合は、便利な小さなコンテキストマネージャーを記述しました。

from contextlib import contextmanager
import logging

@contextmanager
def disable_logger(name):
    """Temporarily disable a specific logger."""
    logger = logging.getLogger(name)
    old_value = logger.disabled
    logger.disabled = True
    try:
        yield
    finally:
        logger.disabled = old_value

その後、次のように使用します。

class MyTestCase(TestCase):
    def test_something(self):
        with disable_logger('<logger name>'):
            # code that causes the logger to fire

これには、ロガーがwith完了するとロガーが再び有効になる(または以前の状態に戻る)という利点があります。


1

これを単体テスト__init__.pyファイルの最上位ディレクトリに置くことができます。これにより、単体テストスイートでグローバルにロギングが無効になります。

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)

0

私の場合settings/test.py、テスト目的で特別に作成された設定ファイルがあります。 これは次のようになります。

from .base import *

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'test_db'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

環境変数DJANGO_SETTINGS_MODULE=settings.testをに設定しました/etc/environment


0

テスト、開発、本番用に異なる初期化モジュールを使用している場合は、何でも無効にしたり、イニシャルでリダイレクトしたりできます。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に置き換えられ、引き続きログが取得されますが、運用コードベースに触れる必要はありません。


0

あなたが使用している場合pytest

pytestはログメッセージをキャプチャし、失敗したテストについてのみ表示するため、通常はログを無効にする必要はありません。代わりに、settings.pyテスト用に別のファイルを使用して(例:)、test_settings.pyそれに追加します。

LOGGING_CONFIG = None

これはDjangoにロギングの設定を完全にスキップするように指示します。のLOGGING設定は無視され、設定から削除することができます。

このアプローチでは、成功したテストのログは取得されず、失敗したテストのすべての利用可能なログが取得されます。

テストは、によって設定されたログを使用して実行されpytestます。pytest設定(例tox.ini)でお好みに合わせて設定できます。デバッグレベルのログメッセージを含めるには、log_level = DEBUG(または対応するコマンドライン引数)を使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.