blueFast
blueFast

Reputation: 44371

Discard stdout / stderr of program under test, but keep unittest output

I have this sample code (test_it.py):

import sys

def give_me_5():
    print >>sys.stdout, "STDOUT"
    print >>sys.stderr, "STDERR"
    return 6

import unittest


class TestMe(unittest.TestCase):

    def setUp(self):
        pass

    def test_give_me_5(self):
        self.assertEqual(give_me_5(), 5)


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

Which gives me the following output:

» python -m unittest test_it
A long annoying output message
A long annoying error message
F
======================================================================
FAIL: test_give_me_5 (__main__.TestMe)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "xx.py", line 17, in test_give_me_5
    self.assertEqual(give_me_5(), 5)
AssertionError: 6 != 5

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

But because of the long, annoying messages produced by the program under test (the real program produces LOTS of output), I am unable to see the output from unittest. I would like to get rid of the stdout / stderr of the function being tested (give_me_5), but I still want to see stdout / stderr of unittest. I would like to get this result:

» python -m unittest test_it
F
======================================================================
FAIL: test_give_me_5 (__main__.TestMe)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "xx.py", line 17, in test_give_me_5
    self.assertEqual(give_me_5(), 5)
AssertionError: 6 != 5

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

So that the output produced by the program under test (both stdout and stderr) is filtered out by unittest, but the output produced by unittest itself is kept. I do not want to modify the code being tested (no redirection in the code itself). I just want to tell unittest that all output to stdout/stderr generated by the code under test should be discarded.

Is this possible?

Upvotes: 5

Views: 1452

Answers (3)

blueFast
blueFast

Reputation: 44371

For reference (and to get comments), here is the code I have ended up using (inspired in the reply by @Alik):

import sys

def give_me_5():
    print >>sys.stdout, "A long annoying output message"
    print >>sys.stderr, "A long annoying error message"
    return 6


import unittest

def redirect(stdout_file=None, stderr_file=None):
    new_stdout = open(stdout_file, 'w') if stdout_file else None
    new_stderr = open(stderr_file, 'w') if stderr_file else None
    if new_stdout : sys.stdout = new_stdout
    if new_stderr : sys.stderr = new_stderr

def redirect_testcase(test_case):
    logfile = '.'.join([test_case.__module__, test_case.__class__.__name__, 'out'])
    errfile = '.'.join([test_case.__module__, test_case.__class__.__name__, 'err'])
    redirect(logfile, errfile)


class TestMe(unittest.TestCase):

    def setUp(self):
        redirect_testcase(self)

    def test_give_me_5(self):
        self.assertEqual(give_me_5(), 5)

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

Now simply by doing a redirect_testcase(self), stdout / stderr get redirected to test_it.TestMe.out / test_it.TestMe.err, and the output from unittest is visible in the console stdout / stderr (and can be redirected via shell if needed).

There is a problem (that I do not yet know how to fix): all tests in a specific TestCase will overwrite the .out / .err files. It would be good to open a different out / log file for each test (as opposed to a common one for each TestCase)

Upvotes: 0

sobolevn
sobolevn

Reputation: 18080

@Alik suggestion was right. But I guess it could be improved.

import sys
# StringIO is replaced in Python 3:
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

class ReplaceStd(object):
    """ Let's make it pythonic. """

    def __init__(self):
        self.stdout = None
        self.stderr = None

    def __enter__(self):
        self.stdout = sys.stdout
        self.stderr = sys.stderr

        # as it was suggseted already:
        sys.stdout = StringIO()
        sys.stderr = StringIO()

    def __exit__(self, type, value, traceback):
        sys.stdout = self.stdout
        sys.stderr = self.stderr

print('I am here')
with ReplaceStd():
    print('I am not')

print('I am back')

And the output:

I am here
I am back

Upvotes: 6

Konstantin
Konstantin

Reputation: 25339

Temporarily replace sys.stdout and sys.stderr with file-like instances. For example, you can use StringIO aka memory buffers.

from StringIO import StringIO

.....


class TestMe(unittest.TestCase):

    def setUp(self):
        pass

    def test_give_me_5(self):

        stdout = sys.stdout
        stderr = sys.stderr

        sys.stdout = StringIO()
        sys.stderr = StringIO()

        self.assertEqual(give_me_5(), 5)

        sys.stdout = stdout
        sys.stderr = stderr

You might want to add exception handling or even turn this code in a context manager to reuse it

Upvotes: 4

Related Questions