2

I want to test with unittest a function in Python (2.7) that use different raw_input.

How can I achieve this?

Function (in module fc):

def main():
    name            = raw_input("name: ").lower()
    surname         = raw_input("surname: ").lower()
    birth_date    = raw_input("Birth date (dd/mm/yyyy): ").lower()
    city          = raw_input("city: ").lower()
    sex           = raw_input("sex (m/f): ").lower()
    #other tasks...

test function:

import fc
import unittest

class test_main_fc(unittest.TestCase):

    def test_main(self):
        #how can I give to main the parameters that will ask?
        self.assertEqual(fc.main(), 'rssmra80a01l781k')             

if __name__ == '__main__':  
    unittest.main()

The solution I could find, this, works for 1 input passed at a time. I want to know how to pass different values to the main function.

This works for only 1 value of raw_input requested, in this case, name.

class test_main_fc(unittest.TestCase):

    def test_fc_output(self):

        original_raw_input = __builtins__.raw_input
        __builtins__.raw_input = lambda _: 'mario'

        #capturing the output
        with captured_output() as (out, err):
            fc.main()
            output = out.getvalue().strip()

        self.assertEqual(output, 'rssmra80a01l781k')

        __builtins__.raw_input = original_raw_input
3
  • In general it is better not to test the function that calls raw_input, but the function that uses its results. So either there is a function that processes values (regardless of where they were obtained from), or you inject into main a function to use for getting input. The former case seems cleaner in general. The latter case allows you to stub the function in tests so it returns what you want. Commented Jan 14, 2018 at 18:38
  • In my case, I should test the code without changing it, so I was looking for a way. Commented Jan 14, 2018 at 18:45
  • If this is an absolute requirement (I would argue with colleagues whether that were the case), then use Ulrich Eckhardt's suggestion, where his fake_input is assigned to __builtins__.raw_input (as shown in the link you posted). Commented Jan 14, 2018 at 18:49

1 Answer 1

1

In order to achieve that, you need to replace raw_input with a function that returns different things when called multiple times, e.g. one like this:

answers = [1, 2, 4]
fake_input = answers.pop
# 4
print(fake_input())
# 2
print(fake_input())
# 1
print(fake_input())
# raise IndexError
print(fake_input())

You install this similar to the existing function:

# replace raw_input
original_raw_input = __builtins__.raw_input
__builtins__.raw_input = ['input', 'fake', 'my'].pop
# ... code that uses raw_input
print raw_input()
print raw_input()
print raw_input()
# restore raw_input
__builtins__.raw_input = original_raw_input

Some notes:

  • I don't know why the original author used __builtins__.raw_input instead of just assigning to raw_input (which requires declaring it as global raw_input when in a function, of course). I haven't tried if it makes any difference.
  • In your test, if the output is not as expected, the original raw_input is not restored! While this may be acceptable in a test, it is still a bug. The easy way out is to use try ... finally, restoring raw_input in the finally code. There's a better way though: Use a so-called context manager. captured_out is one example, it redirects the output for some code and guarantees that the original output target is restored. Take this as exercise, it's very useful to understand this technique.
  • The order is reversed, since pop removes elements from the back. You could first feed the sequence to reversed, but that would make the code even less readable and understandable. Wrapping it into a context manager would hide these details.
  • Don't forget to upgrade to Python 3!
Sign up to request clarification or add additional context in comments.

6 Comments

It gives me an error when in the code is called the lower() function: AttributeError: 'builtin_function_or_method' object has no attribute 'lower'
Wait: Do you understand how the code you have works for one input?
I'm new to Python, so not very well. I assume that original_raw_input redefines the builtin function raw_input, so that when it's called, I could use original_raw_input?
In __builtins__.raw_input is the function that is called when some code calls raw_input. The hack you use changes this function to a different function to simulate input. It used a lambda function lambda _:'mario' which always returned the same value. My code here was just intended to show you how to write a function that returns different values by using a list's pop function. You can't take this code as it stands and plug it into your code though, you need to take some time to understand this and to adapt it, like returning the expected strings instead of some random integers.
Ok solved. Re-assuming: I save the __builtins__.raw_input function in original_raw_input, then I ovveride it with the lambda function: __builtins__.raw_input = lambda _: fake_input(). The function fake_input() uses the pop function on a list of values. Then I reset the __builtins__.raw_input. Python is really fascinating.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.