开发者

How to assert output with nosetest/unittest in python?

I'm writing tests for a function like next one:

def foo():
    print 'hel开发者_开发技巧lo world!'

So when I want to test this function the code will be like this:

import sys
from foomodule import foo
def test_foo():
    foo()
    output = sys.stdout.getline().strip() # because stdout is an StringIO instance
    assert output == 'hello world!'

But if I run nosetests with -s parameter the test crashes. How can I catch the output with unittest or nose module?


I use this context manager to capture output. It ultimately uses the same technique as some of the other answers by temporarily replacing sys.stdout. I prefer the context manager because it wraps all the bookkeeping into a single function, so I don't have to re-write any try-finally code, and I don't have to write setup and teardown functions just for this.

import sys
from contextlib import contextmanager
from StringIO import StringIO

@contextmanager
def captured_output():
    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

Use it like this:

with captured_output() as (out, err):
    foo()
# This can go inside or outside the `with` block
output = out.getvalue().strip()
self.assertEqual(output, 'hello world!')

Furthermore, since the original output state is restored upon exiting the with block, we can set up a second capture block in the same function as the first one, which isn't possible using setup and teardown functions, and gets wordy when writing try-finally blocks manually. That ability came in handy when the goal of a test was to compare the results of two functions relative to each other rather than to some precomputed value.


If you really want to do this, you can reassign sys.stdout for the duration of the test.

def test_foo():
    import sys
    from foomodule import foo
    from StringIO import StringIO

    saved_stdout = sys.stdout
    try:
        out = StringIO()
        sys.stdout = out
        foo()
        output = out.getvalue().strip()
        assert output == 'hello world!'
    finally:
        sys.stdout = saved_stdout

If I were writing this code, however, I would prefer to pass an optional out parameter to the foo function.

def foo(out=sys.stdout):
    out.write("hello, world!")

Then the test is much simpler:

def test_foo():
    from foomodule import foo
    from StringIO import StringIO

    out = StringIO()
    foo(out=out)
    output = out.getvalue().strip()
    assert output == 'hello world!'


Since version 2.7, you do not need anymore to reassign sys.stdout, this is provided through buffer flag. Moreover, it is the default behavior of nosetest.

Here is a sample failing in non buffered context:

import sys
import unittest

def foo():
    print 'hello world!'

class Case(unittest.TestCase):
    def test_foo(self):
        foo()
        if not hasattr(sys.stdout, "getvalue"):
            self.fail("need to run in buffered mode")
        output = sys.stdout.getvalue().strip() # because stdout is an StringIO instance
        self.assertEquals(output,'hello world!')

You can set buffer through unit2 command line flag -b, --buffer or in unittest.main options. The opposite is achieved through nosetest flag --nocapture.

if __name__=="__main__":   
    assert not hasattr(sys.stdout, "getvalue")
    unittest.main(module=__name__, buffer=True, exit=False)
    #.
    #----------------------------------------------------------------------
    #Ran 1 test in 0.000s
    #
    #OK
    assert not hasattr(sys.stdout, "getvalue")

    unittest.main(module=__name__, buffer=False)
    #hello world!
    #F
    #======================================================================
    #FAIL: test_foo (__main__.Case)
    #----------------------------------------------------------------------
    #Traceback (most recent call last):
    #  File "test_stdout.py", line 15, in test_foo
    #    self.fail("need to run in buffered mode")
    #AssertionError: need to run in buffered mode
    #
    #----------------------------------------------------------------------
    #Ran 1 test in 0.002s
    #
    #FAILED (failures=1)


A lot of these answers failed for me because you can't from StringIO import StringIO in Python 3. Here's a minimum working snippet based on @naxa's comment and the Python Cookbook.

from io import StringIO
from unittest.mock import patch

with patch('sys.stdout', new=StringIO()) as fakeOutput:
    print('hello world')
    self.assertEqual(fakeOutput.getvalue().strip(), 'hello world')


In python 3.5 you can use contextlib.redirect_stdout() and StringIO(). Here's the modification to your code

import contextlib
from io import StringIO
from foomodule import foo

def test_foo():
    temp_stdout = StringIO()
    with contextlib.redirect_stdout(temp_stdout):
        foo()
    output = temp_stdout.getvalue().strip()
    assert output == 'hello world!'


I'm only just learning Python and found myself struggling with a similar problem to the one above with unit tests for methods with output. My passing unit test for foo module above has ended up looking like this:

import sys
import unittest
from foo import foo
from StringIO import StringIO

class FooTest (unittest.TestCase):
    def setUp(self):
        self.held, sys.stdout = sys.stdout, StringIO()

    def test_foo(self):
        foo()
        self.assertEqual(sys.stdout.getvalue(),'hello world!\n')


Writing tests often shows us a better way to write our code. Similar to Shane's answer, I'd like to suggest yet another way of looking at this. Do you really want to assert that your program outputted a certain string, or just that it constructed a certain string for output? This becomes easier to test, since we can probably assume that the Python print statement does its job correctly.

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

Then your test is very simple:

def test_foo_msg():
    assert 'hello world' == foo_msg()

Of course, if you really have a need to test your program's actual output, then feel free to disregard. :)


Both n611x007 and Noumenon already suggested using unittest.mock, but this answer adapts Acumenus's to show how you can easily wrap unittest.TestCase methods to interact with a mocked stdout.

import io
import unittest
import unittest.mock

msg = "Hello World!"


# function we will be testing
def foo():
    print(msg, end="")


# create a decorator which wraps a TestCase method and pass it a mocked
# stdout object
mock_stdout = unittest.mock.patch('sys.stdout', new_callable=io.StringIO)


class MyTests(unittest.TestCase):

    @mock_stdout
    def test_foo(self, stdout):
        # run the function whose output we want to test
        foo()
        # get its output from the mocked stdout
        actual = stdout.getvalue()
        expected = msg
        self.assertEqual(actual, expected)


Based on Rob Kennedy's answer, I wrote a class-based version of the context manager to buffer the output.

Usage is like:

with OutputBuffer() as bf:
    print('hello world')
assert bf.out == 'hello world\n'

Here's the implementation:

from io import StringIO
import sys


class OutputBuffer(object):

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

    def __enter__(self):
        self.original_stdout, self.original_stderr = sys.stdout, sys.stderr
        sys.stdout, sys.stderr = self.stdout, self.stderr
        return self

    def __exit__(self, exception_type, exception, traceback):
        sys.stdout, sys.stderr = self.original_stdout, self.original_stderr

    @property
    def out(self):
        return self.stdout.getvalue()

    @property
    def err(self):
        return self.stderr.getvalue()


Or consider using pytest, it has built-in support for asserting stdout and stderr. See docs

def test_myoutput(capsys): # or use "capfd" for fd-level
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
    print("next")
    captured = capsys.readouterr()
    assert captured.out == "next\n"


Unittest ships with a context manager now (Python 3.7, but maybe earlier versions as well). You can just do this:

# example.py

import logging

def method_with_logging():
    logging.info("Hello, World!")

Then in your unit test:

# test.py

from unittest import TestCase
from example import method_with_logging

class TestExample(TestCase):
    def test_logging(self):
        with self.assertLogs() as captured:
            method_with_logging()
        self.assertEqual(len(captured.records), 1) # check that there is only one log message
        self.assertEqual(captured.records[0].getMessage(), "Hello, World!") # and it is the proper one

Taken from https://pythonin1minute.com/how-to-test-logging-in-python/


Building on all the awesome answers in this thread, this is how I solved it. I wanted to keep it as stock as possible. I augmented the unit test mechanism using setUp() to capture sys.stdout and sys.stderr, added new assert APIs to check the captured values against an expected value and then restore sys.stdout and sys.stderr upon tearDown(). I did this to keep a similar unit test API as the built-inunittestAPI while still being able to unit test values printed tosys.stdoutorsys.stderr`.

import io
import sys
import unittest


class TestStdout(unittest.TestCase):

    # before each test, capture the sys.stdout and sys.stderr
    def setUp(self):
        self.test_out = io.StringIO()
        self.test_err = io.StringIO()
        self.original_output = sys.stdout
        self.original_err = sys.stderr
        sys.stdout = self.test_out
        sys.stderr = self.test_err

    # restore sys.stdout and sys.stderr after each test
    def tearDown(self):
        sys.stdout = self.original_output
        sys.stderr = self.original_err

    # assert that sys.stdout would be equal to expected value
    def assertStdoutEquals(self, value):
        self.assertEqual(self.test_out.getvalue().strip(), value)

    # assert that sys.stdout would not be equal to expected value
    def assertStdoutNotEquals(self, value):
        self.assertNotEqual(self.test_out.getvalue().strip(), value)

    # assert that sys.stderr would be equal to expected value
    def assertStderrEquals(self, value):
        self.assertEqual(self.test_err.getvalue().strip(), value)

    # assert that sys.stderr would not be equal to expected value
    def assertStderrNotEquals(self, value):
        self.assertNotEqual(self.test_err.getvalue().strip(), value)

    # example of unit test that can capture the printed output
    def test_print_good(self):
        print("------")

        # use assertStdoutEquals(value) to test if your
        # printed value matches your expected `value`
        self.assertStdoutEquals("------")

    # fails the test, expected different from actual!
    def test_print_bad(self):
        print("@=@=")
        self.assertStdoutEquals("@-@-")


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

When the unit test is run, the output is:

$ python3 -m unittest -v tests/print_test.py
test_print_bad (tests.print_test.TestStdout) ... FAIL
test_print_good (tests.print_test.TestStdout) ... ok

======================================================================
FAIL: test_print_bad (tests.print_test.TestStdout)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tests/print_test.py", line 51, in test_print_bad
    self.assertStdoutEquals("@-@-")
  File "/tests/print_test.py", line 24, in assertStdoutEquals
    self.assertEqual(self.test_out.getvalue().strip(), value)
AssertionError: '@=@=' != '@-@-'
- @=@=
+ @-@-


----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)


I like sorens' straightforward [Answer][1] to the question and sample code, particularly since I'm not familiar with newer features like patch/mock. sorens didn't suggest a way to make the custom assertion methods of the example code's TestStdIO class reusable without resorting to cut/paste, so I took the approach of making TestStdIO a "mixin" class defined in its own module (teststdoutmethods.py in the following example). Since the usual unittest.TestCase-provided assert method references used in TestStdIO will also be available in the test case class, I removed the import unittest line from his sample code and also the derivation of TestStdIO from unittest.TestCase in the class declaration, i.e.,

import io
import sys

class TestStdIO(object):
    def setUp(self):
        ...

Otherwise the code of TestStdIO is as sorens' version sans the two example usages at the end. I used this mixin class version of TestStdIO in some simple unittest test cases of a class in one of the basic example text games in Ch. 2 of Kinsley and McGugan's Beginning Python Game Programming with PyGame, e.g.

import unittest
from teststdoutmethods import TestStdIO   # sorens' TestStdIO as a mixin.
from tank import Tank  # From Beginning Python Game Programming with PyGame.

class Test_Tank_fire(TestStdIO, unittest.TestCase):   # Note multiple inheritance.

    def test_Tank_fire_wAmmo(self):
        oTank1 = Tank('Bill', 5, 100)
        oTank2 = Tank('Jim', 5, 100)

        self.setUp()
        oTank1.fire_at(oTank2)

        self.assertStdoutEquals("Bill fires on Jim\nJim is hit!")
        self.assertEqual(str(oTank1), 'Bill (100 Armor, 4 Ammo)', 'fire_at shooter attribute results incorrect')
        self.assertTrue(str(oTank2) == 'Jim (80 Armor, 5 Ammo)', 'fire_at target attribute results incorrect')

        self.tearDown()

    def test_Tank_fire_woAmmo(self):
        oTank1 = Tank('Bill', 5, 100)
        oTank2 = Tank('Jim', 5, 100)

        # Use up 5 allotted shots.
        for n in range(5):
            oTank1.fire_at(oTank2)

        self.setUp()
        # Try one more.
        oTank1.fire_at(oTank2)

        self.assertStdoutEquals("Bill has no shells!")

        self.tearDown()
    
    def test_Tank_explode(self):
        oTank1 = Tank('Bill', 5, 100)
        oTank2 = Tank('Jim', 5, 100)

        # Use up 4 shots.
        for n in range(4):
            oTank1.fire_at(oTank2)

        self.setUp()
        # Fifth shot should finish the target.
        oTank1.fire_at(oTank2)

        self.assertStdoutEquals("Bill fires on Jim\nJim is hit!\nJim explodes!")
        self.tearDown()

        self.assertTrue(str(oTank2) == 'Jim (DEAD)', 'fire_at target __str__ incorrect when Dead')

The test cases (both successes and ginned failures) worked in Python 3.7. Note that sorens' technique captures all of the stdout output between the setup() and teardown() calls, so I placed these around the specific actions that would generate the specific output I wanted to check. I presume my mixin approach is what sorens would have intended for general reuse, but I'd like to know if anyone has a different recommendation. Thx. [1]: https://stackoverflow.com/a/62429695/7386731

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜