我将它用于一个测试套件,其中有许多 subprocess.run 调用需要检查。
class CmdMatch(NamedTuple):
cmd: str
match: str = ".*"
result: str = ""
side_effect: Callable = None
@contextlib.contextmanager
def mock_run(*cmd_match: Union[str, CmdMatch], **kws):
sub_run = subprocess.run
mock = MagicMock()
if isinstance(cmd_match[0], str):
cmd_match = [CmdMatch(*cmd_match, **kws)]
def new_run(cmd, **_kws):
check_cmd = " ".join(cmd[1:])
mock(*cmd[1:])
for m in cmd_match:
if m.cmd in cmd[0].lower() and re.match(m.match, check_cmd):
if m.side_effect:
m.side_effect()
return subprocess.CompletedProcess(cmd, 0, m.result, "")
assert False, "No matching call for %s" % check_cmd
subprocess.run = new_run
yield mock
subprocess.run = sub_run
所以现在你可以写一个这样的测试:
def test_call_git():
with mock_run("git", "describe.*", "v2.5") as cmd:
do_something()
cmd.assert_called_once()
def test_other_calls():
with mock_run(CmdMatch("git", "describe.*", "v2.5"), CmdMatch("aws", "s3.*links.*", side_effect=CalledProcessError) as cmd:
with pytest.raises(CalledProcessError):
do_something()
几个你可能想改变的时髦的东西,但我喜欢它们:
- 生成的模拟调用忽略第一个参数(因此 which/full-paths 不是测试的一部分)
- 如果没有匹配任何调用,它断言(你必须有一个匹配)
- 第一个位置被认为是“命令的名称”
- 要运行的 kwargs 被忽略(_call 断言很容易但很松散)