0

I like TDD, so I try to write my Black Box Test at first.

This is a python programme that deal with stdin and output to stdout like this (I try to write my own language that just deal with stdin and stdout):

$ python3 ./minor.py
>>> print, "hello\nthis is a good morning"
... hello
  . this is a good morning
>>> $quit

But I can not mock the stdin and stdout. I try to use subprocess in Python but the Popen.stdout.read() is hanging for a EOF, which need the programme killed. Or the communicate() but it will kill my programme and it cannot deal with two or more input.

It upset me for 2+ days, I cannot find anything useful about mock or black-box test with stdin/stdout (It looks strange that I can test with browser but not stdin/stdout easily).

Thanks.

*** First Editing ***

I create a new unittest class to handle my class. It have a function to create a new Popen object.

I try to write to stdin and assert the stdout... But it is hanging just because it cannot find the EOF.

How should I deal with it to make it? Thanks for your help!

class TestFunc(unittest.TestCase):
    def run_minor(self):
        return Popen(['python3', './minor.py'],
                stdin = PIPE,
                stdout = PIPE,
                stderr = PIPE,
                text = True,
            )

    def test_print(self):
        prop = self.run_minor()

        self.assertEqual(prop.stdout.read(), '>>> ')

        prop.stdin.write("print, 'this'")
        self.assertEqual(prop.stdout.read(), '... this\n>>> ')

        prop.stdin.write("$quit")
        self.assertEqual(prop.stdout.read(), '')

        prop.kill()
Peterlits Zo
  • 476
  • 1
  • 4
  • 17
  • I'm not sure I follow what you mean when you say you can't mock stdin and stdout -- `sys.stdin` and `sys.stdout` are just file-like objects, you can replace them with other file-like objects that behave however your tests require. There are cases in which you need to actually have overridden FD 0 and FD 1, but `os.dup2()` makes short work of those cases. – Charles Duffy Aug 20 '20 at 17:40
  • 2
    ...so, can you show us what you did to _try_ to mock stdin or stdout, and an explicit failure mode you encountered in the attempt? – Charles Duffy Aug 20 '20 at 17:43
  • @CharlesDuffy OK, I am editing... – Peterlits Zo Aug 20 '20 at 17:52
  • Does this answer your question? [Python 3 unittest simulate user input](https://stackoverflow.com/questions/1329172/python-3-unittest-simulate-user-input) – Evgeny Aug 21 '20 at 06:12
  • @Evgeny , No and yes... I am thinking because of your comment, and I can not mock it with another function! Just becuase I need to test it but not mock it. The thing that I want to mock is the `input/output`. By the way, I am thinking if use a function to handle input/output like a string is OK for me... input, then turn it to string and return a string, then output... Or build a class with state. – Peterlits Zo Aug 21 '20 at 07:34
  • Your second edit looks like something that should be added as an answer rather than an edit to the question. (Answering your own questions is perfectly acceptable!) – Charles Duffy Aug 21 '20 at 07:41
  • (Also, there's no reason to use edit markers here -- the goal should be to make your question make as much sense as possible to people who are seeing it fort he first time; people who have interacted with it before and want to see the differences can always just check the edit history). – Charles Duffy Aug 21 '20 at 07:42

1 Answers1

1

I create a helper class to help... I do not know if it is the best idea, but it works greatly:

class MinorMock(object):
    """
    The Mock of Minor Programme.

    Use method `input` and `assertOutput` to set the input and the output want. Use
    `assertInputMeetOutput` to check if the input is meet its output by unittest's object.
    """
    def __init__(self, test_obj):
        self.popen = Popen(['python3', 'minor.py'], stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True)
        self.input_text = ''
        self.output_text = ''
        self.err_text = ''
        self.test_obj = test_obj

    def input(self, text):
        self.input_text += text
        return self

    def assertOutput(self, text):
        self.output_text += text
        return self

    def assertError(self, text):
        self.err_text += text
        return self
    
    def assertInputMeetOutput(self):
        (out, err) = self.popen.communicate(self.input_text)
        self.test_obj.assertEqual(out, self.output_text)
        self.test_obj.assertEqual(err, self.err_text)
        self.popen.kill()

Welcome for other answers...

Peterlits Zo
  • 476
  • 1
  • 4
  • 17