pythonuser-inputpytestgui-testing

Making pytest wait for user input


I am writing some tests that rely on user input to decide whether they have passed.

I have this function:

def viewable(actual_proj):
    print("\nCan you see %s projects named:\n"%len(actual_proj))
    for i in actual_proj:
        print (i+"\n")
    return input("(y/n)? : ")

Within :

def is_present(pytestconfig, project_not_present = 0):

    actual_projects = all_projects.copy()
    if (project_not_present!=0):
        del_file = all_ini_files[project_not_present-1]
        os.rename(del_file, del_file +'_tst')
        del actual_projects[project_not_present-1]
    capmanager = pytestconfig.pluginmanager.getplugin('capturemanager')

    subprocess.Popen('./MultiPRM.exe')
    capmanager.suspendcapture(in_=True)

    decision = viewable(actual_projects)
    capmanager.resumecapture()
    if (project_not_present!=0):
        os.rename(del_file+'_tst', del_file)
    if (decision =='y'):
        return True
    else:
        return False

When i run the command pytest name_of_test_file.py it runs fine, and stops after each test to get user input. However, i want to use a file which sets up various variables and headers for a log file (called run_tests.py)

# start the report
print("Creating test report: " + os.path.abspath(report_filepath))
rep = open(report_filepath, "w")
rep.write(report_header)
rep.write("Test environment: \n");
rep.write("  Username: " + os.environ['USERNAME'] + "\n")
rep.write("Testing started at: " + get_time() + "\n\n")
rep.close()

# get application version
cmd = exe_under_test + " --help >> " + report_filepath
os.system(cmd)

# start the tests
cmd = "pytest >> " + report_filepath 
os.system(cmd)

# finalise the report
rep = open(report_filepath, "a+")
rep.write("\nTesting completed at: " + get_time() + "\n\n")
rep.close()

When i run it this way, it does not stop or run any of the tests.

If i could write to a log file while also writing the same thing to the terminal(including user input) that would be great. Otherwise, a way of calling this function correctly would work too.


Solution

  • Your tests should be as easy to run as possible to make it hassle free to execute them. If they will be dependent on external (e.g. user's) input and some other tricks to properly run, then nobody is going to execute them in the long run.

    If you are the sole developer in the project you can probably live with it, but I would say that this approach is incorrect and not considered to be the best-practices.

    First of all if you are just waiting for user's input in the console (which seems like from your code snippets), then just mock the input built-in and set it return value, e.g.:

    app.py

    def my_input(prompt=''):
        try:
            return raw_input(prompt)
        except NameError:
            return input(prompt)
    
    
    def my_great_func():
        choice = my_input('y/n?: ')
        return choice
    

    test.py

    import unittest.mock as mock    
    import pytest
    
    from app import my_great_func
    
    @pytest.yield_fixture
    def fake_input():
        with mock.patch('app.my_input') as m:
            yield m
    
    
    def test_my_great_func(fake_input):
        fake_input.return_value = 'y'
        assert my_great_func() == 'y'
    

    Test execution:

    $ pytest test.py -v
    ============================= test session starts ==============================
    platform linux -- Python 3.5.2, pytest-3.2.1
    cachedir: .cache
    rootdir: /home/kris/projects/tmp, inifile:
    collected 1 item                                                                
    
    test.py::test_my_great_func PASSED
    
    =========================== 1 passed in 0.01 seconds ===========================
    

    And secondly strive to write code where your application logic and GUI are loosely coupled - this way you can test your logic regardless of the GUI (be it web, desktop or mobile app).