使用pytest猴子补丁模拟pandas Series.to_csv仍生成文件的问题排查
pytest猴子补丁模拟pandas Series.to_csv仍生成文件的问题排查
问题描述
我正在测试module.py里的write_query()函数,用pytest的monkeypatch fixture来猴子补丁Series.to_csv(),目的是防止实际写入文件。猴子补丁的函数能执行预期的检查,也能抛出验证测试通过的错误,但奇怪的是,磁盘上还是生成了一个叫doesnt_matter的文件,这是为什么?
附上相关代码:
folder/module.py
import pandas as pd def write_query(df, outpath, entry_name_col): # 生成查询列表,build_query()不涉及pandas对象/方法 entry_names = build_query(df, entry_name_col) # 将查询写入csv with open(outpath, 'w') as csv_file: pd.Series(entry_names).to_csv(csv_file, index=False, lineterminator='\n', header = False)
folder/test_module.py
import os import pytest from folder.module import build_query, write_query def temp_to_csv(self, csv_file, index, lineterminator, header): # 检查csv_file是否正确 assert csv_file.name == 'doesnt_matter' # 检查要写入的值是否正确 if list(self.values) == ['(id:"entry_name_1")', '(id:"entry_name_2")']: # 抛出特定错误,用于测试验证 raise ValueError('Correct values passed to to_csv') else: raise ValueError class TestWriteQuery: def test_normal(self, good_input_queries, # conftest.py中的dataframe fixture monkeypatch): # 检查函数调用前文件不存在 assert 'doesnt_matter' not in os.listdir(os.getcwd()) # 此断言通过 # 猴子补丁Series.to_csv(),尝试过多种路径写法 monkeypatch.setattr('folder.module.pd.Series.to_csv', temp_to_csv) # 调用函数并验证抛出的错误 with pytest.raises(ValueError) as e: # 此断言通过 write_query(good_input_queries, outpath = 'doesnt_matter', entry_name_col = 'Entry name') assert 'Correct values passed to to_csv' in str(e) # 此断言通过 # 检查文件未被写入 assert 'doesnt_matter' not in os.listdir(os.getcwd()) # 此断言失败
问题根源
问题出在write_query函数里的文件打开操作!
你猴子补丁的是Series.to_csv(),但with open(outpath, 'w') as csv_file:这一行在进入with代码块时,就已经执行了open函数——open(outpath, 'w')模式本身就会在磁盘上创建一个空文件(如果文件不存在的话)。这个操作和你的猴子补丁完全无关,哪怕to_csv根本没往文件里写内容,文件也已经被创建出来了。
解决方案
方法1:猴子补丁open函数(推荐)
既然文件是open创建的,我们可以在测试里猴子补丁内置的open函数,让它返回一个模拟的文件对象,不实际创建磁盘文件:
def test_normal(self, good_input_queries, monkeypatch): # 确保初始状态下文件不存在 assert 'doesnt_matter' not in os.listdir(os.getcwd()) # 模拟文件对象 mock_file = mock.MagicMock() def mock_open(*args, **kwargs): # 验证打开的路径是否正确 assert args[0] == 'doesnt_matter' return mock_file # 猴子补丁内置open函数 monkeypatch.setattr('builtins.open', mock_open) # 同时猴子补丁to_csv monkeypatch.setattr('folder.module.pd.Series.to_csv', temp_to_csv) with pytest.raises(ValueError) as e: write_query(good_input_queries, outpath='doesnt_matter', entry_name_col='Entry name') assert 'Correct values passed to to_csv' in str(e) # 现在文件不会被创建,断言通过 assert 'doesnt_matter' not in os.listdir(os.getcwd())
方法2:修改测试断言,允许空文件存在
如果你不想猴子补丁open,可以修改最后一个断言,检查文件是否为空(因为猴子补丁的to_csv没往里面写内容),测试后记得清理:
# 替换原有的文件存在检查 assert os.path.getsize('doesnt_matter') == 0 # 测试完成后删除临时文件 os.remove('doesnt_matter')
这种方法会在磁盘上创建临时空文件,需要手动清理,不如方法1干净。
方法3:重构原函数(可选)
如果可以修改原函数,你可以直接利用pd.Series.to_csv的path_or_buf参数,省去手动打开文件的步骤:
def write_query(df, outpath, entry_name_col): entry_names = build_query(df, entry_name_col) # 直接传入路径给to_csv,无需手动open pd.Series(entry_names).to_csv( outpath, index=False, lineterminator='\n', header=False )
这样文件的创建和写入都由to_csv处理,你的猴子补丁就能完全阻止文件生成了。
备注:内容来源于stack exchange,提问作者Tim Kirkwood
相关产品推荐
相关产品推荐

