I can't test that, it uses STDOUT (Python)
You're working with some Python code, and would like to write a test, but...
"I can't test that - it uses STDOUT!"
Okay, well, that's really not such a big problem to handle. The solutions to this problem are actually simple enough that you can apply them to many other situations that might otherwise encourage you to skip tests just this one time (again).
In our modern age, we have the advantage of ubiquitous LLMs in browsers, IDEs, and CLIs. We shouldn't be stuck on common problems anymore, nor must we remain unaware of features in our test libraries.
Rather than look at print statements as a brick wall, you can get started writing useful tests right away.
So, let's talk about a few options:
Pytest "capsys" fixture.
This is pretty trivial. You import and mention 'capsys' in your code, and you have access to the content printed to stdout and stderror. These are what we would call "listener fakes" - they listen to, and remember, what information was passed to them.
It's just that easy.
def test_print(capsys):
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
This is very easy and is especially useful when you're facing a tough function that already existed before you came along.
UnitTest "patch"
If you like unittest (which i do), you can get the same kind of functionality
from unittest.mock import patch
import unittest
class TestPrintMock(unittest.TestCase):
@patch("builtins.print")
def test_print_called(self, mock_print):
print("hello")
mock_print.assert_called_with("hello")
This is yet another listening fake. Just like the pytest fixture, it temporarily patches the print statement, though you may have to be a bit more careful -- you will need to patch the "builtins.print" in the system under test, not in the test file. Just the same, it works nicely enough.
Contextlib.redirect_stdout
Yet another listening fake is the redirect, though you have to pass it a StringIO object. That may actually be more convenient than the listening mock shown above. It resembles the capsys option more.
import unittest
import io
from contextlib import redirect_stdout, redirect_stderr
class TestOutput(unittest.TestCase):
def test_print_output(self):
f = io.StringIO()
with redirect_stdout(f):
print("hello")
self.assertEqual(f.getvalue(), "hello\n")
Change the Function Signature
One may add a file stream to the signature of a method and have it print to the stream rather than directly to standard out.
The parameter can default to stdout.
This is a preferred and more hexagonally correct approach.
Then the tests can create a StringIO object and pass it as the stream to write. The method does its normal thing (unaware that the standard output has been replaced), and you have an easy way to check the output of the function.
I list this last because people faced with a function that doesn't have tests are often squeamish about changing the function before it has tests. A bit of caution will not draw any chastisement from me.
And more...
You can imagine that there are other ways, and you could build your own, but these are already built in to the wonderful world of Python, so they are low-hanging fruit.
If you can provide me with a few more of your favourite tricks, I may include them here.
Beware Overspecification
When it comes to text capture and assertions, one can certainly get caught up in overspecification. You may initially want to write your assertions against the entire text printed by a function, but this is unwise.
If your test will fail because of a difference in spacing, tabs, or capitalization, then you may have a fragile test on your hands. Likewise, if adding a date or or other informational prefix to the message will cause your test to fail.
The tricky bit is to write an assertion that is accurate to prove that the scenario provided by your test is properly evidenced, but without overspecifying the results.
Nobody loves a fragile test.
I have written tests that trim, lowercase, and split the text output. I've used word ordering and regexes to match within strings. I've tried a lot of things that worked. I've not managed yet to come up with a good test assertion that reads "the message intends to inform us that 'bluetick.md' is unreadable" -- which is okay. That is the kind of test that might better be written using exceptions rather than text.
The innovation coming from trade show exhibit builders in Los Angeles is inspiring. Their ability to turn ideas into immersive booths is impressive. Trade Show Booth rental los angeles
ReplyDelete