如何用pytest与unittest.mock模拟paramiko.SSHClient并修复测试报错
解决Paramiko SFTP单元测试中Mock导致的TypeError问题
问题背景
我在给基于Paramiko的SFTP交互代码写单元测试,需求是:传入两个远程文件路径,其中一个触发IOError,验证失败的文件不会被包含在返回结果里。但测试运行时,在callback(io.BytesIO(b))这行报TypeError: a bytes-like object is required, not 'MagicMock',查不到Mock的问题出在哪。
错误原因
这个报错的核心是:你Mock的SFTP文件对象的read()方法返回的是MagicMock实例,而不是真实的字节流。当代码把这个Mock对象传给io.BytesIO()时,就会触发类型错误。另外如果Mock的层级不对(比如没有正确模拟文件对象的上下文管理器行为),也会导致类似问题。
修复方案
要正确Mock Paramiko的SFTPClient及其返回的文件对象,确保:
- 正常文件的
read()方法返回真实字节数据 - 故障文件的打开/读取操作抛出
IOError - 上下文管理器(
with语句)的行为被正确模拟
完整测试示例
假设被测代码是这样的:
# your_module.py import paramiko import io def process_sftp_files(sftp: paramiko.SFTPClient, remote_paths: list, callback): success_paths = [] for path in remote_paths: try: with sftp.open(path, 'rb') as f: file_content = f.read() callback(io.BytesIO(file_content)) success_paths.append(path) except IOError: continue return success_paths
对应的单元测试代码可以这么写:
import unittest from unittest.mock import Mock, patch import io from your_module import process_sftp_files class TestSFTPFileProcessing(unittest.TestCase): def test_process_with_one_failed_file(self): # 1. 创建Mock的SFTPClient,用spec保证方法和真实类一致 mock_sftp = Mock(spec=paramiko.SFTPClient) # 2. 模拟正常文件的行为:返回字节数据,正确处理上下文管理器 mock_valid_file = Mock() mock_valid_file.read.return_value = b"sample valid content" mock_valid_file.__enter__.return_value = mock_valid_file # 3. 模拟故障文件的行为:打开时抛出IOError mock_failed_file = Mock() mock_failed_file.__enter__.side_effect = IOError("Permission denied or file missing") # 4. 给sftp.open设置不同调用的返回值/异常 mock_sftp.open.side_effect = [mock_valid_file, mock_failed_file] # 5. Mock回调函数,用来验证调用情况 mock_callback = Mock() # 6. 执行被测函数 result = process_sftp_files( mock_sftp, ["/remote/valid/file.txt", "/remote/failed/file.txt"], mock_callback ) # 7. 验证结果 # 成功列表只包含正常文件 self.assertEqual(result, ["/remote/valid/file.txt"]) # 回调只被调用一次,且参数是正确的BytesIO self.assertEqual(mock_callback.call_count, 1) callback_arg = mock_callback.call_args[0][0] self.assertIsInstance(callback_arg, io.BytesIO) self.assertEqual(callback_arg.read(), b"sample valid content")
关键注意点
- 用
spec=paramiko.SFTPClient创建Mock,避免出现真实SFTPClient没有的方法,保证Mock的真实性 - 必须模拟文件对象的
__enter__方法,因为被测代码用了with语句,这个方法返回的是文件本身 - 给
side_effect传入列表,让open方法在不同调用时返回不同结果(正常文件/抛出异常) - 确保
read()返回的是真实字节,而不是默认的Mock对象
内容的提问来源于stack exchange,提问作者reikje
相关产品推荐
相关产品推荐

