Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make pytest wait for (manual) user action?

Tags:

python

pytest

We are sucessfully using pytest (Python 3) to run a test suite testing some hardware devices (electronics). For a subset of these tests, we need the tester to change the hardware arrangement, and afterwards change it back. My approach was to use a module-level fixture attached to the tests in question (which are all in a separate module), with two input calls:

@pytest.fixture(scope="module")
def disconnect_component():
    input('Disconnect component, then press enter')
    yield  # At this point all the tests with this fixture are run
    input('Connect component again, then press enter')

When running this, I get OSError: reading from stdin while output is captured. I can avoid this by calling pytest with --capture=no, and have confirmed that my approach works, meaning I get the first query before the test subset in question, and the second one after they have run.

The big drawback is that this deactivates capturing stdin/stderr for the whole test suite, which some of the other test rely on.

I also tried to use capsys.disabled (docs) like this

@pytest.fixture(scope="module")
def disconnect_component(capsys):
    with capsys.disabled():
        input('Disconnect component, then press enter')
        yield  # At this point all the tests with this fixture are run
        input('Connect component again, then press enter')

but when running this I get ScopeMismatch: You tried to access the 'function' scoped fixture 'capsys' with a 'module' scoped request object, involved factories.

Can I make pytest wait for user action in some other way than input? If not, can I disable capturing just for the tests using above fixture?

like image 747
Christoph Avatar asked Mar 13 '17 09:03

Christoph


People also ask

Why pytest is taking too long?

In summary, if you have slow compiling speeds with pytest, try specifying the directory or file your test(s) are in. Or use norecursedirs to skip directories that don't have any tests, like src or . git . in my case it takes 20 seconds to load, even by specifying a test file with 2 unit tests inside.

How do you call pytest fixture directly?

To access the fixture function, the tests have to mention the fixture name as input parameter. Pytest while the test is getting executed, will see the fixture name as input parameter. It then executes the fixture function and the returned value is stored to the input parameter, which can be used by the test.

How do I stop pytest from running?

pytest has the option -x or --exitfirst which stops the execution of the tests instanly on first error or failed test. pytest also has the option --maxfail=num in which num indicates the number of errors or failures required to stop the execution of the tests.


2 Answers

So, I found a hint by a pytest dev, based on which I basically do what the capsys.disable() function does:

@pytest.fixture(scope="module")
def disconnect_component(pytestconfig):
    capmanager = pytestconfig.pluginmanager.getplugin('capturemanager')

    capmanager.suspend_global_capture(in_=True)
    input('Disconnect component, then press enter')
    capmanager.resume_global_capture()

    yield  # At this point all the tests with this fixture are run

    capmanager.suspend_global_capture(in_=True)
    input('Connect component again, then press enter')
    capmanager.resume_global_capture()

This works flawlessly as far as I can see. Don't forget the in_=True bit.

Edit: From pytest 3.3.0 (I think), capmanager.suspendcapture and capmanager.resumecapture were renamed to capmanager.suspend_global_capture and capmanager.resume_global_capture, respectively.

like image 149
Christoph Avatar answered Sep 28 '22 00:09

Christoph


As of pytest 5, as a fixture, you can use this:

@pytest.fixture
def suspend_capture(pytestconfig):
    class suspend_guard:
        def __init__(self):
            self.capmanager = pytestconfig.pluginmanager.getplugin('capturemanager')
        def __enter__(self):
            self.capmanager.suspend_global_capture(in_=True)
        def __exit__(self, _1, _2, _3):
            self.capmanager.resume_global_capture()

    yield suspend_guard()

Example usage:

def test_input(suspend_capture):
    with suspend_capture:
        input("hello")
like image 26
Erik Aronesty Avatar answered Sep 28 '22 00:09

Erik Aronesty