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?
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.
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.
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.
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.
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")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With