1

I have a python3.7 script, which takes a YAML file as input and processes it depending on the instructions within. The YAML file I am using for unit testing looks like this:

... tasks: - echo '1' - echo '2' - echo '3' - echo '4' - echo '5' 

The script loops over tasks and then runs each one, using os.system() call.

The manual testing indicates, that the output is as expected:

1 2 3 4 5 

But I can't make it work in my unit test. Here's how I'm trying to capture the output:

from application import application from io import StringIO import unittest from unittest.mock import patch class TestApplication(unittest.TestCase): def test_application_tasks(self): expected = ['1','2','3','4','5'] with patch('sys.stdout', new=StringIO()) as fakeOutput: application.parse_event('some event') # print() is called here within parse_event() self.assertEqual(fakeOutput.getvalue().strip().split(), expected) 

When running python3 -m unittest discover -s tests, all I get is AssertionError: Lists differ: [] != ['1', '2', '3', '4', '5'].

I also tried using with patch('sys.stdout', new_callable=StringIO) as fakeOutput: instead, but to no avail.

Another thing I tried was self.assertEqual(fakeOutput.getvalue(), '1\n2\n3\n4\n5'), and here what the unittest outputs:

AssertionError: '' != '1\n2\n3\n4\n5' + 1 + 2 + 3 + 4 + 5 

Obviously, the script works and outputs the right result, but fakeOutput does not capture it.

Using patch as a decorator does not work either:

from application import application from io import StringIO import unittest from unittest.mock import patch class TestApplication(unittest.TestCase): @patch('sys.stdout', new_callable=StringIO) def test_application_tasks(self): expected = ['1','2','3','4','5'] application.parse_event('some event') # print() is called here within parse_event() self.assertEqual(fakeOutput.getvalue().strip().split(), expected) 

Would output absolutely the same error: AssertionError: Lists differ: [] != ['1', '2', '3', '4', '5']

3
  • Your question is incomplete. Where is the definition of application? A trivial definition with print("Here is some output") does not reproduce the behavior you describe. Commented Aug 9, 2019 at 12:51
  • Based on the mention of os.system though I'll take a guess... Commented Aug 9, 2019 at 12:52
  • @Jean-PaulCalderone yeah, I already realized it... what confused me is that I started using echo right away Commented Aug 9, 2019 at 12:54

3 Answers 3

1

os.system runs a new process. If you monkey-patch sys.stdout this affects the current process but has no consequences for any new processes.

Consider:

import sys from os import system from io import BytesIO capture = sys.stdout = BytesIO() system("echo Hello") sys.stdout = sys.__stdout__ print(capture.getvalue()) 

Nothing is captured because only the child process has written to its stdout. Nothing has written to the stdout of your Python process.

Generally, avoid os.system. Instead, use the subprocess module which will let you capture output from the process that is run.

Sign up to request clarification or add additional context in comments.

Comments

1

Thank you, Jean-Paul Calderone. I realized the fact, that os.system() creates a completely different process and therefore I need to tackle the problem differently, only after I posted the question :)

To actually be able to test my code, I had to rewrite it using subprocess instead of os.system(). In the end, I went with subprocess_run_result = subprocess.run(task, shell=True, stdout=subprocess.PIPE) and then getting the result using subprocess_run_result.stdout.strip().decode("utf-8").

In the tests I just create an instance of class and call a method, which runs the tasks in subprocess.

My whole refactored code and tests are here in this commit if anyone would like to take a look.

1 Comment

If Jean-Paul's answer helped you, you should just mark it as correct instead of posting your own. Stack Overflow is a wiki. You can actually just edit other people's answers if you have something to add, instead of posting new answers that reference other answers.
1

Your solution is fine, just use getvalue instead, like so:

with patch("sys.stdout", new_callable=StringIO) as f: print("Foo") r = f.getvalue() print("r: {r!r} ;".format(r=r)) 

r: "Foo" ;

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.