我有一个带有命令行接口的外部工具,通过使用 sys.stdin in select.select([sys.stdin], [], [], 0)[0]
并相应调整预期的参数。我通过以下方式调用这个外部工具 subprocess
并依赖于没有通过stdin输入的用例。
现在,我想通过以下方法对这个功能进行自动集成测试 pytest
. 但我不能让它工作,如果不提供的 pytest
命令行选项 --capture=sys
禁用所有测试的文件描述符级别的捕获。在任何其他情况下(也是在使用 capfd.disabled()
或 capsys.disabled()
因为这些只禁用stdout和stderr,但不禁用stdin捕获),外部工具检测到会有通过stdin提供的输入,触发对其他参数的错误结论,从而导致我的测试失败。
为了举例说明,我基本上有如下文件。
external_script.py:
import select
import sys
print(sys.stdin in select.select([sys.stdin], [], [], 0)[0])
if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
print(sys.stdin.readline().strip())
print(sys.argv)
internal_part.py:
import subprocess
def call_external():
popen = subprocess.Popen(["/usr/bin/python3", "external_script.py", "1"],
stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print(popen.communicate()[:2])
if __name__ == "__main__":
call_external()
test.py:
import internal_part
def test(capsys):
with capsys.disabled(): # or capfd.disabled()
internal_part.call_external()
当我现在通过python运行internal_part.py时,我得到了预期的输出,即 sys.stdin
的调用没有返回 select.select
因此,在stdin中没有任何输入。但当我运行 test.py
通过 pytest
橆 --capture=sys
我得到的输出显示数据是通过stdin提供的。然后从sys.stdin中读取数据,得到一个空字符串。
如果 select.select
调用,而在内部部分,我至少会得到一个错误信息,即这是一个不支持的操作,因为伪文件是重定向的 stdin。对于外部工具,在它自己的解释器中运行,除了错误地检测到stdin提供输入外,我没有得到任何迹象表明出了问题。
有没有什么选项可以让这个测试方案工作,例如,通过禁用这个特定测试的 stdin 捕获,而不禁用该测试套件中所有测试的文件描述符级别的捕获?
如果你愿意在生产代码中生成并显式传递一个尚未准备好读取的文件描述符1您可以通过以下方式生成一个新的命名管道 os.pipe
并将其读取端作为stdin传递。很显然,生成一个新的管道,唯一的目的就是它是空的,这对奇怪的接口来说有点变通。但只要你不向管道中写入任何东西,管道应该还没有准备好读取,因此,在 select.select
调用不应该接收它。内部部分就可以有点像下面的代码。
import contextlib
import os
import subprocess
@contextlib.contextmanager
def not_ready_to_read():
try:
read_end, write_end = os.pipe()
yield read_end
finally:
os.close(read_end)
os.close(write_end)
def call_external():
with not_ready_to_read() as not_ready_stdin:
popen = subprocess.Popen(["/usr/bin/python3", "external_script.py", "1"],
stdin=not_ready_stdin, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
print(popen.communicate()[:2])
if __name__ == "__main__":
call_external()
1这也可以帮助您解决其他情况,即通过以下方式测试 select.select
错误地暗示一些数据是通过stdin传递的,比如你通过 slurm
作业调度器。因此,它不是以启用测试为唯一目的的生产代码的改编。