0

I have inherited a program made by someone else which makes considerable effort to be "interactive", using the following kind of syntax:

x = input("What is the value of x")

There are dozens of input statements in nested if blocks. To start refactoring this program I need to set up some benchmarks which can cover the whole code and automatically input all combinations of user input.

What is a quick way to get the program to accept user input via a python script?

EDIT

I have tried the windows alternative to pexpect which seems to work OK.

import wexpect

child = wexpect.spawn('python input_script.py')
child.expect('input x')
child.sendline('5')

The test file input_script.py is as follows:

x = input('input x')
print('{} was your input'.format(x))

The caller script seems to run with an exit code of 0 so no errors. However I would like a way to see all the standard output, including the "sent lines". I have tried placing child.before and child.after but I cannot get the entire output to show.

2
  • You could do something like stackoverflow.com/a/21875308/3001761 Commented Aug 21, 2020 at 10:24
  • The library Pexpect is designed specifically for this (actually you can use it for any command line interactive program, not just Python). For simple cases, you can just spawn a new process and get pipes to stdin and stdout, as proposed in the question linked in the previous comment. If you don't want to spawn a new process, you could temporarily replace sys.stdin and sys.stdout (and sys.stderr if necessary) with io.StringIO objects before calling the inherited code. Commented Aug 21, 2020 at 10:45

3 Answers 3

3

One possibility is to mock the input function with the builtin unittest.mock:

import builtins
from unittest.mock import patch
with patch('builtins.input') as input_mock:
    input_mock.side_effect = [
        'Input 1',
        'Input 2',
        'Input 3',
    ]
    print(input('First input'))
    # Input 1
    print(input())
    # Input 2
    print(input('Last one'))
    # Input 3
Sign up to request clarification or add additional context in comments.

3 Comments

This is interesting as I am intending to make these calls part of an eventual benchmark test suite. However for now I would like to also be able to do it outside of a testing framework. I assume something similar can be done with pytest?
@user32882 I don't really have experience with that, but I'd imagine so. There is this package pytest-mock, or maybe it works using this mock context manager as part of an initailization / finalization fixture.
Upon second thought I think that mocking is the way to go... I have found many Q&A's on here and other websites (code-maven.com/mocking-input-and-output-for-python-testing, for example) which show that this pretty much the most elegant way to solve the problem...
0

You have few options:

  • load a config file
  • use cli arguments (sys.argv)
  • use env variables (os.getenv(..))

I would go with getting 1 input from the caller. This input points to a config file (json,yaml,ini) that holds rest of the input you need. So you will change the logic of the code and ask fro the user input only when you can data in the config. Example:

size = config.get('size')
size = size if size is not None else input('type the size please')

If you want to entirely replace input() you can go with something like the below code.

import sys
config = {'type size please:':12}

def input(prompt):
  value = config.get(prompt)
  if value is not None:
    return value
  else:
    print(prompt)
    return sys.stdin.readline()

size = input('type size please:')
print(size)
number = input('type number please:')
print(number)

5 Comments

I think the OP means how do they make this work without initially removing all of the input calls.
@jonrsharpe OK - I have added code to handle this situation. Thanks.
Yes... I don't want to remove them initially, though eventually that is the goal...
Ok. I have created a input replacement. Look at the answer please.
Entirely replaceing input is not the way to go in my opinion. It involves 1) overriding a built-in function and 2) modifying the existing code. I want to do neither of these things. Sorry.
0

The solution I ended up going with is something like this:

Caller script:

from subprocess import Popen, PIPE, STDOUT
import sys
user_input = ['John', '555']

communicate_argument = '\n'.join(user_input)
p = Popen([sys.executable, 'example2.py'], stdout=PIPE, stdin=PIPE, stderr=STDOUT, encoding='utf-8')

stdout, stderr = p.communicate(communicate_argument)

print(stdout)

Called script:

name = input('What is your name\n')
age = input('What is your age\n')

print('You are {}, and you are {} years old'.format(name, age))

I feel that this is sufficiently simple and generalizable such that I can rapidly run the program in different ways. Unfortunately I cannot get the user input itself to show up in stdout but I'm going to have to live with that for the time being.

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.