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-in
unittestAPI while still being able to unit test values printed to
sys.stdoutor
sys.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
精彩评论