Testing Logger
First, make your loggers parameters to your functions and classes. Globals make testing difficult.
class Example:
def __init__(self, logger):
self._logger = logger
def do_something(self):
self._logger.info("Logged something!")
def example(logger):
logger.info("Logged something!")
Now you can use the TestingLogger
during tests. If it quacks like a duck, its a duck. Let’s make a TestingLogger
that quacks like a logger.
class TestingLogger:
def __init__(self):
self.messages = []
def debug(self, message):
self._log(message)
def info(self, message):
self._log(message)
def warning(self, message):
self._log(message)
def error(self, message):
self._log(message)
def critical(self, message):
self._log(message)
def exception(self, message):
self._log(message)
def _log(self, message):
self.messages.append(message)
This class implements the methods you will call 99% of the time against a logger. Instead of logging though, it will capture the messages in a messages
list. You can make assertions against this list in your tests.
def test_example_function():
logger = TestingLogger()
example(logger)
assert "Logged something!" == logger.messages[0]
def test_example_class():
logger = TestingLogger()
Example(logger).do_something()
assert "Logged something!" == logger.messages[0]
You can expand on this idea in many ways:
- Capture the log level if you need to assert against them.
- Inherit from
list
and useself.append
to capture logs. Then you can make assertions against the testing logger directly instead oflogger.messages
. - Capture the
str
value of exceptions instead of the instances inTestingLogger.exception
. - If passing loggers as parameters is cumbersome, you can create a default when one isn’t specified, giving you flexibility in tests and convenience in the rest of the places.
If you would like to learn more about this testing style, check out the Python architecture book. It is one of the best programming books I have read in a long time.
As an Amazon Associate, I earn from qualifying purchases