コード内の特定の条件により、ログメッセージがdjangoログに書き込まれるようにしたいです。Djangoユニットテストフレームワークでこれをどのように行うのですか?
送信メールを確認するのと同じように、ログに記録されたメッセージを確認できる場所はありますか?私の単体テストは拡張されdjango.test.TestCase
ます。
コード内の特定の条件により、ログメッセージがdjangoログに書き込まれるようにしたいです。Djangoユニットテストフレームワークでこれをどのように行うのですか?
送信メールを確認するのと同じように、ログに記録されたメッセージを確認できる場所はありますか?私の単体テストは拡張されdjango.test.TestCase
ます。
モジュールを使用しmock
て、ロギング モジュールまたはロガー オブジェクトをモックします。それが終わったら、ロギング関数が呼び出される引数を確認してください。
たとえば、次のようにコーディングするとします。
import logging
logger = logging.getLogger('my_logger')
logger.error("Your log message here")
次のようになります。
from unittest.mock import patch # For python 2.x use from mock import patch
@patch('this.is.my.module.logger')
def test_check_logging_message(self, mock_logger):
mock_logger.error.assert_called_with("Your log message here")
The common way of mocking out the logger object (see the splendid chap Simeon Visser's answer) is slightly tricky in that it requires the test to mock out the logging in all the places it's done. This is awkward if the logging comes from more than one module, or is in code you don't own. If the module the logging comes from changes name, it will break your tests.
The splendid 'testfixtures' package includes tools to add a logging handler which captures all generated log messages, no matter where they come from. The captured messages can later be interrogated by the test. In its simplest form:
Assuming code-under-test, which logs:
import logging
logger = logging.getLogger()
logger.info('a message')
logger.error('an error')
A test for this would be:
from testfixtures import LogCapture
with LogCapture() as l:
call_code_under_test()
l.check(
('root', 'INFO', 'a message'),
('root', 'ERROR', 'an error'),
)
The word 'root' indicates the logging was sent via a logger created using logging.getLogger()
(i.e. with no args.) If you pass an arg to getLogger (__name__
is conventional), that arg will be used in place of 'root'.
The test does not care what module created the logging. It could be a sub-module called by our code-under-test, including 3rd party code.
The test asserts about the actual log message that was generated, as opposed to the technique of mocking, which asserts about the args that were passed. These will differ if the logging.info call uses '%s' format strings with additional arguments that you don't expand yourself (e.g. use logging.info('total=%s', len(items))
instead of logging.info('total=%s' % len(items))
, which you should. It's no extra work, and allows hypothetical future logging aggregation services such as 'Sentry' to work properly - they can see that "total=12" and "total=43" are two instances of the same log message. That's the reason why pylint warns about the latter form of logging.info
call.)
LogCapture includes facilities for log filtering and the like. Its parent 'testfixtures' package, written by Chris Withers, another splendid chap, includes many other useful testing tools. Documentation is here: http://pythonhosted.org/testfixtures/logging.html
テスト クラスを使用している場合は、次のソリューションを使用できます。
import logger
from django.test import TestCase
class MyTest(TestCase):
@classmethod
def setUpClass(cls):
super(MyTest, cls).setUpClass()
cls.logging_error = logging.error
logging.error = cls._error_log
@classmethod
def tearDownClass(cls):
super(MyTest, cls).tearDownClass()
logging.error = cls.logging_error
@classmethod
def _error_log(cls, msg):
cls.logger = msg
def test_logger(self):
self.assertIn('Message', self.logger)
error
このメソッドは、モジュールの関数logging
をテスト目的でのみカスタム メソッドに置き換え、stdout をcls.logger
変数に入れます。この変数は、 を呼び出してすべてのテスト ケースで使用できますself.logger
。最後に、error
関数をlogging
モジュールから元に戻すことにより、変更を元に戻します。