如何正确Mock subprocess.check_output()?yt-dlp单元测试问题
解决subprocess.check_output的Mock问题:针对yt-dlp文件大小获取的单元测试
问题背景
正在测试__float__方法,该方法通过subprocess.check_output()调用yt-dlp获取可下载文件的近似大小,再转换为MB并返回。尝试两种Mock方案均失败:
- 直接Mock
check_output始终返回0.0,未生效; - Mock
Popen触发AttributeError: __enter__,即使尝试Mock__enter__也无法解决。
待测试方法代码:
def __float__(self): try: bytesize = subprocess.check_output(f"yt-dlp -O filesize_approx {self.url}".split(), stderr=subprocess.STDOUT) megabytes = auxillary.bytesto(bytesize, 'm') # 转换为MB self.length = round(megabytes, 2) except subprocess.CalledProcessError: return -2.0 return self.length
原测试代码(Mock Popen失败版本):
@patch("subprocess.Popen") def test__float__handlesSpecialCode(self, mock_subproc_popen): """-1.0 length表示大小尚未计算""" process_mock = Mock() attrs = {"communicate.return_value": ("output", "error")} process_mock.configure_mock(**attrs) mock_subproc_popen.return_value = process_mock obj = TFile("https://www.youtube.com/watch?v=GtL1huin9EE", -1.0) self.assertAlmostEqual(15.83, float(obj)) # 验证返回值 self.assertNotEqual(-1, obj.length) # 验证self.length被更新
问题分析
- 直接Mock check_output失败原因:Mock路径错误——需patch代码中实际引用的
subprocess实例,而非全局subprocess模块。 - Mock Popen失败原因:
subprocess.run使用了上下文管理器(with Popen(...) as process),Mock的进程对象未实现__enter__方法,同时缺少returncode(命令执行状态)和stdout(输出内容)的正确设置。
有效解决方案
方案1:直接Mock subprocess.check_output(推荐)
跳过内部Popen逻辑,直接Mockcheck_output的返回值,是最简洁的方式。
修改后的测试代码:
from unittest.mock import patch, Mock from core.file import TFile import subprocess class TestTFile(unittest.TestCase): @patch("core.file.subprocess.check_output") def test__float__returns_correct_size(self, mock_check_output): # 模拟yt-dlp返回的字节数(对应15.83MB左右) mock_check_output.return_value = b'16600000' obj = TFile("https://www.youtube.com/watch?v=GtL1huin9EE", -1.0) result = float(obj) self.assertAlmostEqual(15.83, result) self.assertEqual(15.83, obj.length) # 验证调用参数是否正确 mock_check_output.assert_called_once_with( ["yt-dlp", "-O", "filesize_approx", "https://www.youtube.com/watch?v=GtL1huin9EE"], stderr=subprocess.STDOUT )
方案2:正确Mock subprocess.Popen
若需Mock底层Popen,需让Mock对象支持上下文管理器,并设置必要属性。
修改后的测试代码:
from unittest.mock import patch, Mock from core.file import TFile class TestTFile(unittest.TestCase): @patch("core.file.subprocess.Popen") def test__float__handlesSpecialCode(self, mock_popen): # 创建支持上下文管理器的进程Mock process_mock = Mock() # 实现__enter__方法,返回自身 process_mock.__enter__.return_value = process_mock # 设置stdout输出(字节串格式) process_mock.stdout.read.return_value = b'16600000' # 设置命令执行成功的返回码 process_mock.returncode = 0 mock_popen.return_value = process_mock obj = TFile("https://www.youtube.com/watch?v=GtL1huin9EE", -1.0) result = float(obj) self.assertAlmostEqual(15.83, result) self.assertNotEqual(-1.0, obj.length) # 验证Popen被正确调用 mock_popen.assert_called_once()
关键注意事项
- Mock路径正确性:必须patch代码中实际导入的
subprocess实例(如core.file.subprocess.check_output),而非全局subprocess模块。 - 返回值类型:
check_output返回字节串(bytes),Mock返回值需为b'xxx'格式,而非字符串。 - 上下文管理器支持:Mock
Popen时,必须实现__enter__方法返回自身,否则会触发__enter__属性错误。
内容的提问来源于stack exchange,提问作者Thops
相关产品推荐
相关产品推荐

