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