问题
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?
回答1:
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.
回答2:
Maybe it's worth noting that above solution doesn't have to be in a fixture. I've made a helper function for that:
import pytest
def ask_user_input(msg=''):
""" Asks user to check something manually and answer a question
"""
notification = "\n\n???\tANSWER NEEDED\t???\n\n{}".format(msg)
# suspend input capture by py.test so user input can be recorded here
capture_manager = pytest.config.pluginmanager.getplugin('capturemanager')
capture_manager.suspendcapture(in_=True)
answer = raw_input(notification)
# resume capture after question have been asked
capture_manager.resumecapture()
logging.debug("Answer: {}".format(answer))
return answer
回答3:
For future reference, if you need to use input
with pytest
. You can do this in any part of your pytest, setup_class
, test_...
, teardown_method
, etc. This is for pytest > 3.3.x
import pytest
capture_manager = pytest.config.pluginmanager.getplugin('capturemanager')
capture_manager.suspend_global_capture(in_=True)
answer = input('My reference text here')
capture_manager.resume_global_capture()
回答4:
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")
回答5:
Solutions that use the global pytest.config
object no longer work. For my use case, using --capture=sys
together with a custom input()
that uses stdin
and stdout
directly works well.
def fd_input(prompt):
with os.fdopen(os.dup(1), "w") as stdout:
stdout.write("\n{}? ".format(prompt))
with os.fdopen(os.dup(2), "r") as stdin:
return stdin.readline()
来源:https://stackoverflow.com/questions/42760059/how-to-make-pytest-wait-for-manual-user-action