如何在无实际IO操作的前提下测试加载pickle文件的函数
无磁盘IO测试pickle加载函数的pytest实现方案
完全不需要生成test_list.pkl这类实体文件,核心思路是用mock模拟文件操作和pickle反序列化的返回值,直接在内存中注入测试用的字典列表,全程绕过真实磁盘IO。
先修正原函数的逻辑错误
你贴的原代码有两处明显笔误,直接运行会报错/逻辑不成立,先调整后再做测试:
- 列表推导式中错误地用外层
loaded_list做键索引,应该取当前遍历到的entry的对应键 - 仅传入
filter_key参数,没有指定需要匹配的目标值,判断相等时左右两边都是filter_key,过滤逻辑完全失效
修正后的函数代码:
import pickle def load_pickled_list(path_to_file, filter_key=None, filter_value=None): with open(path_to_file, "rb") as file: loaded_list = pickle.load(file) if filter_key is not None and filter_value is not None: # 用get避免条目里不存在对应key时抛KeyError loaded_list = [entry for entry in loaded_list if entry.get(filter_key) == filter_value] return loaded_list
如果你原本的设计就是按key本身的值过滤,可以保留原来的参数逻辑,但是从常规使用场景看,加上filter_value才是可用的实现。
测试用例实现(mock构造测试替身)
可以直接用标准库自带的unittest.mock,或者搭配pytest-mock简化写法,全程不需要生成真实pkl文件。
依赖安装(可选)
如果用pytest-mock的话先安装:pip install pytest pytest-mock
完整测试代码
import pytest from unittest.mock import mock_open, patch # 替换成你自己存放load_pickled_list的模块路径 from my_utils import load_pickled_list # 直接在代码里定义两组测试用的字典列表,不需要序列化到磁盘 TEST_DATASET_1 = [ {"id": 1, "type": "fruit", "name": "apple"}, {"id": 2, "type": "vegetable", "name": "carrot"}, {"id": 3, "type": "fruit", "name": "banana"} ] TEST_DATASET_2 = [ {"uid": 1001, "role": "admin", "active": True}, {"uid": 1002, "role": "user", "active": False}, {"uid": 1003, "role": "user", "active": True} ] # 参数化传入多组测试场景 @pytest.mark.parametrize("mock_load_data,filter_kwargs,expected_result", [ # 场景1:第一组数据,不过滤,返回全量 (TEST_DATASET_1, {}, TEST_DATASET_1), # 场景2:第一组数据,过滤type为fruit的条目 (TEST_DATASET_1, {"filter_key": "type", "filter_value": "fruit"}, [TEST_DATASET_1[0], TEST_DATASET_1[2]]), # 场景3:第二组数据,不过滤,返回全量 (TEST_DATASET_2, {}, TEST_DATASET_2), # 场景4:第二组数据,过滤active为True的条目 (TEST_DATASET_2, {"filter_key": "active", "filter_value": True}, [TEST_DATASET_2[0], TEST_DATASET_2[2]]) ]) def test_load_pickled_list(mocker, mock_load_data, filter_kwargs, expected_result): # 给pickle.load打桩,直接返回我们准备好的测试数据,不需要真的反序列化 mock_pickle_load = mocker.patch("pickle.load", return_value=mock_load_data) # 模拟open操作,不会真的读写磁盘 mock_file_open = mocker.patch("builtins.open", mock_open(read_data=b"")) # 传入任意假路径就行,因为open已经被替换成mock对象了 result = load_pickled_list("fake_dir/mock_file.pkl", **filter_kwargs) # 校验结果符合预期 assert result == expected_result # 校验函数确实按预期调用了文件读取和pickle加载 mock_file_open.assert_called_once_with("fake_dir/mock_file.pkl", "rb") mock_pickle_load.assert_called_once()
不用pytest-mock的原生写法
如果不想装额外插件,用标准库的patch上下文管理器也能实现完全一样的效果:
def test_load_pickled_list_native(): test_data = TEST_DATASET_1 with patch("builtins.open", mock_open(read_data=b"")) as mock_file, \ patch("pickle.load", return_value=test_data) as mock_load: res = load_pickled_list("any_fake_path.pkl") assert res == test_data mock_file.assert_called_once_with("any_fake_path.pkl", "rb") mock_load.assert_called_once()
注意:打桩的时候一定要patch被测函数所在命名空间下的
open和pickle.load,不要patch错路径,不然mock不会生效。
这种写法的测试替身完全跳过了真实的pickle序列化/反序列化和磁盘IO,测试运行速度极快,也不会产生临时文件,所有测试数据都硬编码在用例里,可读性和可维护性都更高。
内容的提问来源于stack exchange,提问作者LuGeNat
相关产品推荐
相关产品推荐

