How should I verify a log message when testing Python code under nose?

前端 未结 11 714
谎友^
谎友^ 2020-12-13 01:12

I\'m trying to write a simple unit test that will verify that, under a certain condition, a class in my application will log an error via the standard logging API. I can\'t

11条回答
  •  伪装坚强ぢ
    2020-12-13 02:14

    UPDATE: No longer any need for the answer below. Use the built-in Python way instead!

    This answer extends the work done in https://stackoverflow.com/a/1049375/1286628. The handler is largely the same (the constructor is more idiomatic, using super). Further, I add a demonstration of how to use the handler with the standard library's unittest.

    class MockLoggingHandler(logging.Handler):
        """Mock logging handler to check for expected logs.
    
        Messages are available from an instance's ``messages`` dict, in order, indexed by
        a lowercase log level string (e.g., 'debug', 'info', etc.).
        """
    
        def __init__(self, *args, **kwargs):
            self.messages = {'debug': [], 'info': [], 'warning': [], 'error': [],
                             'critical': []}
            super(MockLoggingHandler, self).__init__(*args, **kwargs)
    
        def emit(self, record):
            "Store a message from ``record`` in the instance's ``messages`` dict."
            try:
                self.messages[record.levelname.lower()].append(record.getMessage())
            except Exception:
                self.handleError(record)
    
        def reset(self):
            self.acquire()
            try:
                for message_list in self.messages.values():
                    message_list.clear()
            finally:
                self.release()
    

    Then you can use the handler in a standard-library unittest.TestCase like so:

    import unittest
    import logging
    import foo
    
    class TestFoo(unittest.TestCase):
    
        @classmethod
        def setUpClass(cls):
            super(TestFoo, cls).setUpClass()
            # Assuming you follow Python's logging module's documentation's
            # recommendation about naming your module's logs after the module's
            # __name__,the following getLogger call should fetch the same logger
            # you use in the foo module
            foo_log = logging.getLogger(foo.__name__)
            cls._foo_log_handler = MockLoggingHandler(level='DEBUG')
            foo_log.addHandler(cls._foo_log_handler)
            cls.foo_log_messages = cls._foo_log_handler.messages
    
        def setUp(self):
            super(TestFoo, self).setUp()
            self._foo_log_handler.reset() # So each test is independent
    
        def test_foo_objects_fromble_nicely(self):
            # Do a bunch of frombling with foo objects
            # Now check that they've logged 5 frombling messages at the INFO level
            self.assertEqual(len(self.foo_log_messages['info']), 5)
            for info_message in self.foo_log_messages['info']:
                self.assertIn('fromble', info_message)
    

提交回复
热议问题