Djangoアプリケーションをテストするために、単純な単体テストベースのテストランナーを使用しています。
私のアプリケーション自体は、次を使用してsettings.pyで基本的なロガーを使用するように構成されています。
logging.basicConfig(level=logging.DEBUG)
そして、私のアプリケーションコードでは次を使用します:
logger = logging.getLogger(__name__)
logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG))
ただし、unittestsを実行するときは、テスト結果の出力が乱雑にならないようにログを無効にします。テストを実行するときにアプリケーション固有のロガーがコンソールにデータを書き込まないように、グローバルな方法でロギングをオフにする簡単な方法はありますか?
logging.disable(logging.CRITICAL)
CRITICAL
以下のレベルのすべてのロギングコールを無効にします。ロギングを再度有効にできます
logging.disable(logging.NOTSET)
Djangoにいるので、次の行をsettings.pyに追加できます。
_import sys
import logging
if len(sys.argv) > 1 and sys.argv[1] == 'test':
logging.disable(logging.CRITICAL)
_
そうすれば、テストのすべてのsetUp()
にその行を追加する必要はありません。
この方法でテストのニーズに合わせていくつかの便利な変更を行うこともできます。
テストに詳細を追加する別の「より優れた」または「よりクリーンな」方法があり、それが独自のテストランナーを作成しています。
次のようなクラスを作成します。
_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
に カスタムロギングフィルター を追加します。
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 を設定してフィルターを使用します:
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,
},
}
}
最終結果:私がテストしているとき、コンソールには何も行きませんが、他のすべては同じままです。
特定の状況でのみトリガーされるロギング命令を含むコードを設計し、問題が発生した場合に診断に必要な正確なデータを出力する必要があります。そのため、私はtestを実行して、想定されていることを実行するため、ロギングを完全に無効にすることは実行できません。ソフトウェアが実稼働状態になったら、私thoughtが記録されるものが記録されないことを知りたくありません。
さらに、一部のテストランナー(たとえば、Nose)は、テスト中にログをキャプチャし、テストの失敗と共にログの関連部分を出力します。テストが失敗した理由を理解するのに役立ちます。ロギングが完全にオフになっている場合、キャプチャできるものはありません。
Hassekのカスタムテストランナーのアイデアが気に入っています。 DjangoTestSuiteRunner
は、Django 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)
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)
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
はログ出力を生成しません。
簡単なメソッドデコレータを使用して、特定のテストメソッドでのみログを無効にします。
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
Unittestの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)
ログが必要な場合とそうでない場合があります。 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.
継続的インテグレーションフローのテストをスピードアップしたい場合に非常に役立ちます。
テスト、開発、および本番用に異なるイニシャライザーモジュールがある場合は、イニシャライザーで何かを無効にするかリダイレクトできます。 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に置き換えられ、引き続きログを取得することができますが、運用コードベースに触れる必要はありません。
pytest
を使用している場合:
Pytestはログメッセージをキャプチャし、失敗したテストについてのみ表示するため、通常はログを無効にしたくないでしょう。代わりに、テスト用に別のsettings.py
ファイル(たとえば、test_settings.py
)を使用して追加します。
LOGGING_CONFIG = None
これはDjango=ロギングの設定を完全にスキップするように指示します。LOGGING
設定は無視され、設定から削除できます。
このアプローチを使用すると、合格したテストのログは取得されず、失敗したテストのすべての利用可能なログが取得されます。
テストは、pytest
によって設定されたロギングを使用して実行されます。 pytest
の設定(たとえば、tox.ini
)で好みに合わせて設定できます。デバッグレベルのログメッセージを含めるには、log_level = DEBUG
(または対応するコマンドライン引数)を使用します。
私の場合、設定ファイルがあります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
。