You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确Mock subprocess.check_output()?yt-dlp单元测试问题

解决subprocess.check_output的Mock问题:针对yt-dlp文件大小获取的单元测试

问题背景

正在测试__float__方法,该方法通过subprocess.check_output()调用yt-dlp获取可下载文件的近似大小,再转换为MB并返回。尝试两种Mock方案均失败:

  1. 直接Mock check_output始终返回0.0,未生效;
  2. 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被更新

问题分析

  1. 直接Mock check_output失败原因:Mock路径错误——需patch代码中实际引用的subprocess实例,而非全局subprocess模块。
  2. 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'格式,而非字符串。
  • 上下文管理器支持:MockPopen时,必须实现__enter__方法返回自身,否则会触发__enter__属性错误。

内容的提问来源于stack exchange,提问作者Thops

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 21:25:23