2

I am trying to write a unit-test for my module that is written in Python 2.7, I can't migrate to 3.x right now. What i want is for this test do is to check if my module generates an warning logs, if it does then capture it. I couldn't find an answer for Python 2.7 from my searching the web and stack-overflow. i have included a simple testable code that you could use to try things out or understand my question better.

UPDATE: Just to clarify i am willing to change my test case i.e test_warning_2 to be able to catch log.warn current implementation of that method is just a place holder.

import logging
import warnings
from unittest import TestCase

def generate_warning_2():
    logging.warn("this is a warning")


def generate_warning_1():
    warnings.warn("this is a warning")


class TestWarning(TestCase):

    def test_warning_1(self):
        warnings.simplefilter("always")
        with warnings.catch_warnings(record=True) as w:
            generate_warning_1()
            self.assertEquals(len(w), 1)

    def test_warning_2(self):
        # Below code is just a place holder, i need some code to replace this so that i can catch `log.warn`
        warnings.simplefilter("always")
        with warnings.catch_warnings(record=True) as w:
            generate_warning_2()
            self.assertEquals(len(w), 1)

Here if you see function generate_warning_2 you will notice i am using conventional python logging warning that is not captured by my test case. I know the reason is because it doesn't use warnings module. I just wanted to show what i want it to do.

The other function generate_warning_1 i use warnings module to capture warning log, this is my current implementation that works fine.

I would like to be able to catch log.warn instead of having to use warning to achieve this. Is this possible in Python 2.7? Please don't provide answers for Python 3.x, as i already know its possible there.

Hope my question is clear, please feel free to ask me questions or edit where appropriate. Any help here is appreciated.

AJS
  • 1,993
  • 16
  • 26
  • "I can't migrate to 3.x right now." Better find time soon. 2.7 is reaching end of life next year. – jpmc26 Mar 28 '19 at 07:56
  • Also, don't unit testing logging operations. It is not a valuable use of time. – jpmc26 Mar 28 '19 at 07:57
  • i know 2.7 is reaching its end, the decision to not migrate is not mine. – AJS Mar 28 '19 at 08:42
  • You are confusing *warnings* with *logging at loglevel WARN*. These are two very different types of things. You are simply looking for testing if your logging code works, and `warnings.catch_warnings()` **can't help with that**. – Martijn Pieters Apr 02 '19 at 22:14
  • @MartijnPieters i know they are two different things the test case in `test_warning_2` is just a place holder as i am not sure how `log.warn` can be caught in test case to add tests around it . What i want is to replace the current i.e `test_warning_2` test case with what ever the solution may be. – AJS Apr 03 '19 at 06:08
  • 1
    Right, then see the duplicate post, which covers exactly that scenario. – Martijn Pieters Apr 03 '19 at 11:51

1 Answers1

0

This can be solved using a logger handler. Unfortunately there seems to be no way of setting the handler on the root logger, but only on instances returned by getLogger. If you can live with that, this should work:

import logging
import warnings
import unittest


class WarningsHandler(logging.Handler):
    def handle(self, record):
        if record.levelno == logging.WARN:
          warnings.warn(record.getMessage())
        return record

log = logging.getLogger()
log.addHandler(WarningsHandler())

def generate_warning_2():
    log.warn("this is a warning")


def generate_warning_1():
    warnings.warn("this is a warning")


class TestWarning(unittest.TestCase):

    def test_warning_1(self):
        warnings.simplefilter("always")
        with warnings.catch_warnings(record=True) as w:
            generate_warning_1()
            self.assertEquals(len(w), 1)

    def test_warning_2(self):
        warnings.simplefilter("always")
        with warnings.catch_warnings(record=True) as w:
            generate_warning_2()
            self.assertEquals(len(w), 1)

if __name__ == "__main__":
    unittest.main()

$ python2 so.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

In order to prevent eventual double printing, you might want to make the handler return the LogRecord only conditionally.

Hubert Grzeskowiak
  • 15,137
  • 5
  • 57
  • 74
  • Thanks for your answer, had thought about something like this. I want to avoid touching the logging module for now if i can, – AJS Apr 01 '19 at 06:29
  • Then I probably misunderstood the question. You don't want to use the `warnings` module everywhere, but you also do not want to do anything with the `logging` module? – Hubert Grzeskowiak Apr 01 '19 at 06:40
  • Also just to clarify i am willing to change function `test_warning_2` if there is any other way to capture `log.warn` logging – AJS Apr 01 '19 at 06:40
  • i don't want to change the logging module so that i can catch warning in my test case. I am primary want to catch log.warn in my test case any way possible. even if that mean to change the `test_warning_2` test case that is just a place holder as i don't know any other implementation. – AJS Apr 01 '19 at 06:43
  • Do you want to catch the number of warnings or the stderr output? – Hubert Grzeskowiak Apr 01 '19 at 06:45
  • both are fine if my code raises a warning i want my test case to be able to assert or check if there was a `log.warn` raised – AJS Apr 01 '19 at 06:46
  • 1
    @AJS just modify the code such that the `WarningsHandler` stores the information (e.g. store the message in a `warnings` instance variable), and gets instantiated, added and removed by the test case. That way you set up the handler at the start of the test case, run the code, remove the handler, then check the handler's content. Alternatively, switch everything to pytest: it has support for [warnings assertions](https://docs.pytest.org/en/4.4.0/warnings.html#assertwarnings) and [logging assertions](https://docs.pytest.org/en/latest/logging.html#caplog-fixture) built in. – Masklinn Apr 01 '19 at 07:46