How can I simulate input to stdin for pyunit?

前端 未结 5 989
清酒与你
清酒与你 2020-12-03 10:56

I\'m trying to test a function that takes input from stdin, which I\'m currently testing with something like this:

cat /usr/share/dict/words | .         


        
相关标签:
5条回答
  • 2020-12-03 11:05

    Replace sys.stdin with an instance of StringIO, and load the StringIO instance with the data you want returned via sys.stdin. Also, sys.__stdin__ contains the original sys.stdin object, so restoring sys.stdin after your test is as simple as sys.stdin = sys.__stdin__.

    Fudge is a great python mock module, with convenient decorators for doing patching like this for you, with automatic cleanup. You should check it out.

    0 讨论(0)
  • 2020-12-03 11:05

    You didn't describe what sort of code is in spellchecker.py, which gives me freedom to speculate.

    Suppose it's something like this:

    import sys
    
    def check_stdin():
      # some code that uses sys.stdin
    

    To improve testability of check_stdin function, I propose to refactor it like so:

    def check_stdin():
      return check(sys.stdin)
    
    def check(input_stream):
      # same as original code, but instead of
      # sys.stdin it is written it terms of input_stream.
    

    Now most of your logic is in check function, and you can hand-craft whatever input you can imagine in order to test it properly, without any need to deal with stdin.

    My 2 cents.

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

    The short answer is to monkey patch raw_input().

    There are some good examples in the answer to How to display the redirected stdin in Python?

    Here is a simple, trivial example using a lambda that throws away the prompt and returns what we want.

    System Under Test

    cat ./name_getter.py
    #!/usr/bin/env python
    
    class NameGetter(object):
    
        def get_name(self):
            self.name = raw_input('What is your name? ')
    
        def greet(self):
            print 'Hello, ', self.name, '!'
    
        def run(self):
            self.get_name()
            self.greet()
    
    if __name__ == '__main__':
        ng = NameGetter()
        ng.run()
    
    $ echo Derek | ./name_getter.py 
    What is your name? Hello,  Derek !
    

    Test case:

    $ cat ./t_name_getter.py
    #!/usr/bin/env python
    
    import unittest
    import name_getter
    
    class TestNameGetter(unittest.TestCase):
    
        def test_get_alice(self):
            name_getter.raw_input = lambda _: 'Alice'
            ng = name_getter.NameGetter()
            ng.get_name()
            self.assertEquals(ng.name, 'Alice')
    
        def test_get_bob(self):
            name_getter.raw_input = lambda _: 'Bob'
            ng = name_getter.NameGetter()
            ng.get_name()
            self.assertEquals(ng.name, 'Bob')
    
    if __name__ == '__main__':
        unittest.main()
    
    $ ./t_name_getter.py -v
    test_get_alice (__main__.TestNameGetter) ... ok
    test_get_bob (__main__.TestNameGetter) ... ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    
    0 讨论(0)
  • 2020-12-03 11:28

    Update -- using unittest.mock.patch

    Since python 3.3 there is new submodule for unittest called mock that does exactly what you need to do. For those using python 2.6 or above there is a backport of mock found here.

    import unittest
    from unittest.mock import patch
    
    import module_under_test
    
    
    class MyTestCase(unittest.TestCase):
    
        def setUp(self):
            # raw_input is untouched before test
            assert module_under_test.raw_input is __builtins__.raw_input
    
        def test_using_with(self):
            input_data = "123"
            expected = int(input_data)
    
            with patch.object(module_under_test, "raw_input", create=True, 
                    return_value=expected):
                # create=True is needed as raw_input is not in the globals of 
                # module_under_test, but actually found in __builtins__ .
                actual = module_under_test.function()
    
            self.assertEqual(expected, actual)
    
        @patch.object(module_under_test, "raw_input", create=True)
        def test_using_decorator(self, raw_input):
            raw_input.return_value = input_data = "123"
            expected = int(input_data)
    
            actual = module_under_test.function()
    
            self.assertEqual(expected, actual)
    
        def tearDown(self):
            # raw input is restored after test
            assert module_under_test.raw_input is __builtins__.raw_input
    
    if __name__ == "__main__":
        unittest.main()
    # where module_under_test.function is:
    def function():
        return int(raw_input("prompt> "))
    

    Previous answer -- replacing sys.stdin

    I think the sys module might be what you're looking for.

    You can do something like

    import sys
    
    # save actual stdin in case we need it again later
    stdin = sys.stdin
    
    sys.stdin = open('simulatedInput.txt','r') 
    # or whatever else you want to provide the input eg. StringIO
    

    raw_input will now read from simulatedInput.txt whenever it is called. If the contents of simulatedInput was

    hello
    bob
    

    then the first call to raw_input would return "hello", the second "bob" and third would throw an EOFError as there was no more text to read.

    0 讨论(0)
  • 2020-12-03 11:30

    If you are using mock module (written by Michael Foord), in order to mock raw_input function you can use syntax like:

    @patch('src.main.raw_input', create=True, new=MagicMock(return_value='y'))
    def test_1(self):
        method_we_try_to_test();    # method or function that calls **raw_input**
    
    0 讨论(0)
提交回复
热议问题