How to assert output with nosetest/unittest in python?

后端 未结 12 1218
野趣味
野趣味 2020-11-28 02:56

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

def foo():
    print \'hello world!\'

So when I want to test this function the code will b

相关标签:
12条回答
  • 2020-11-28 03:32

    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 讨论(0)
  • 2020-11-28 03:36

    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')
    
    0 讨论(0)
  • 2020-11-28 03:36

    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)
    
    0 讨论(0)
  • 2020-11-28 03:39

    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. :)

    0 讨论(0)
  • 2020-11-28 03:41

    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)
    
    0 讨论(0)
  • 2020-11-28 03:44

    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!'
    
    0 讨论(0)
提交回复
热议问题