macOS下Python Click命令单元测试因沙箱路径差异失败的修复方案
问题
我有一个使用Click库实现命令行界面的Python模块,模块中定义了接收文件路径参数并调用bar函数的foo函数。
模块代码:
import click def bar(src): print(src) @click.command("test") @click.argument("src", nargs=-1, type=click.Path(exists=True, resolve_path=True)) def foo(src): return bar(src)
我用unittest和click.testing.CliRunner在macOS上为该模块编写单元测试,测试代码如下:
import os import unittest import click.testing import tempfile from unittest.mock import patch import temp_module # 包含foo函数的模块 class TestFoo(unittest.TestCase): def setUp(self): self.runner = click.testing.CliRunner() self.temp = tempfile.mkdtemp() self.downloads = os.path.join(self.temp, "downloads") os.mkdir(self.downloads) def runtime(*args): return self.runner.invoke(temp_module.foo, args=args) self.runtime = runtime def test_foo(self): with patch("temp_module.bar") as mock_bar: self.runtime(self.downloads) expected_src = (self.downloads,) mock_bar.assert_called_once_with(self.downloads)
运行测试时出现断言错误:
AssertionError: expected call not found. Expected: bar(('/var/folders/.../downloads',)) Actual: bar(('/private/var/folders/.../downloads',))
原因是macOS沙箱为每个进程创建私有/var目录,导致click.Path(resolve_path=True)返回的路径带有/private前缀。需要修复单元测试,兼顾macOS沙箱路径差异且兼容不同操作系统。
解决方案
方法1:用os.path.realpath统一路径格式
os.path.realpath会自动解析系统的路径映射(包括macOS的/private前缀),把预期路径和实际调用路径都转成真实路径后再断言,保证字符串匹配一致。
修改测试方法的断言部分:
def test_foo(self): with patch("temp_module.bar") as mock_bar: self.runtime(self.downloads) # 统一转成真实路径 expected_path = os.path.realpath(self.downloads) mock_bar.assert_called_once_with((expected_path,))
方法2:用os.path.samefile验证路径指向一致性
不依赖字符串匹配,直接验证两个路径是否指向同一个文件系统对象,这种方式更可靠,完全不受路径前缀差异影响。
修改测试方法:
def test_foo(self): with patch("temp_module.bar") as mock_bar: self.runtime(self.downloads) # 获取实际调用的路径参数 actual_path = mock_bar.call_args[0][0][0] # 验证路径指向同一目录 self.assertTrue(os.path.samefile(self.downloads, actual_path)) # 验证调用次数 self.assertEqual(mock_bar.call_count, 1)
方法3:复用Click的路径解析逻辑
直接使用Click的Path类型处理器来解析预期路径,确保和命令执行时的路径处理逻辑完全一致,从根源上避免格式差异。
修改测试方法的断言部分:
def test_foo(self): with patch("temp_module.bar") as mock_bar: self.runtime(self.downloads) # 用Click的Path类型解析预期路径 path_converter = click.Path(exists=True, resolve_path=True) resolved_path = path_converter.convert(self.downloads, None, None) mock_bar.assert_called_once_with((resolved_path,))
内容的提问来源于stack exchange,提问作者thatch
相关产品推荐
相关产品推荐

