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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 17:52:58