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

如何用pytest测试KFP v2组件?Input/Output与Fixture问题求助

问题:pytest测试KFP v2组件时TaskOutputArgument无path属性报错

在使用pytest对KFP v2.ds1的Kubeflow组件做本地测试时(组件在流水线中可正常运行),处理Input/Output参数与pytest fixture时出现报错:调用组件后返回TaskOutputArgument对象而非预期的Dataset,无法访问path属性。

代码示例

1. 模拟数据集的pytest fixture(同时作为KFP组件)

# ./fixtures/
@pytest.fixture
@component()
def sample_df(dataset: Output[Dataset]):
     df = pd.DataFrame(
         {
             'name': ['Ana', 'Maria', 'Josh'],
             'age': [15, 19, 22],
         }
     )
     dataset.path += '.csv'
     df.to_csv(dataset.path, index=False)
     return

2. 待测试的KFP组件(年龄翻倍功能)

# ./src/
@component()
def double_ages(df_input: Input[Dataset], df_output: Output[Dataset]):
     df = pd.read_csv(df_input.path)
     
     double_df = df.copy()
     double_df['age'] = double_df['age']*2

     df_output.path += '.csv'
     double_df.to_csv(df_output.path, index=False)

3. 测试代码

#./tests/
@pytest.mark.usefixtures("sample_df")
def test_double_ages(sample_df):

    expected_df = pd.DataFrame(
        {
            'name': ['Ana', 'Maria', 'Josh'],
            'age': [30, 38, 44],
        }
    )

    df_component = double_ages(sample_df)
    df_output = df_component.outputs['df_output']
    df = pd.read_csv(df_output.path)
    
    assert df['age'].tolist() == expected_df['age'].tolist()

报错信息

AttributeError: 'TaskOutputArgument' object has no attribute 'path'


解决方案

方法一:直接测试组件核心业务逻辑(推荐)

KFP的@component装饰器会将函数包装为任务构造器,直接调用返回的是任务参数而非执行结果。最高效的方式是剥离核心逻辑单独测试:

步骤1:分离组件业务逻辑

修改待测试组件,把核心逻辑抽成独立函数:

# ./src/
def _double_ages_logic(df: pd.DataFrame) -> pd.DataFrame:
    double_df = df.copy()
    double_df['age'] = double_df['age'] * 2
    return double_df

@component()
def double_ages(df_input: Input[Dataset], df_output: Output[Dataset]):
     df = pd.read_csv(df_input.path)
     double_df = _double_ages_logic(df)
     df_output.path += '.csv'
     double_df.to_csv(df_output.path, index=False)

步骤2:编写逻辑测试

直接测试核心函数,无需依赖KFP的Input/Output机制:

# ./tests/
def test_double_ages_logic():
    sample_df = pd.DataFrame(
        {
            'name': ['Ana', 'Maria', 'Josh'],
            'age': [15, 19, 22],
        }
    )
    expected_df = pd.DataFrame(
        {
            'name': ['Ana', 'Maria', 'Josh'],
            'age': [30, 38, 44],
        }
    )
    
    result_df = _double_ages_logic(sample_df)
    assert result_df['age'].tolist() == expected_df['age'].tolist()

如果不想抽离逻辑,也可以通过组件函数.func访问原始函数,传入模拟的Input/Output对象:

# ./tests/
from kfp.dsl import Dataset, Input, Output
import tempfile
import os

def test_double_ages_direct():
    # 创建临时输入文件
    with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f:
        sample_df = pd.DataFrame(
            {
                'name': ['Ana', 'Maria', 'Josh'],
                'age': [15, 19, 22],
            }
        )
        sample_df.to_csv(f.name, index=False)
    
    # 模拟Input/Output对象
    input_dataset = Dataset(path=f.name)
    output_dataset = Dataset(path=tempfile.mktemp(suffix='.csv'))
    
    # 直接调用原始组件函数
    double_ages.func(Input(input_dataset), Output(output_dataset))
    
    # 验证结果
    expected_df = pd.DataFrame(
        {
            'name': ['Ana', 'Maria', 'Josh'],
            'age': [30, 38, 44],
        }
    )
    result_df = pd.read_csv(output_dataset.path)
    assert result_df['age'].tolist() == expected_df['age'].tolist()
    
    # 清理临时文件
    os.unlink(f.name)
    os.unlink(output_dataset.path)

方法二:使用KFP本地运行器执行完整组件

如果需要测试完整的组件执行流程(包括KFP参数处理),可以用LocalPipelineRunner运行组件:

# ./tests/
from kfp.dsl import Pipeline
from kfp.local import LocalPipelineRunner

def test_double_ages_full():
    # 定义测试流水线
    @Pipeline
    def test_pipeline():
        sample_task = sample_df()
        double_task = double_ages(df_input=sample_task.outputs['dataset'])
        return double_task
    
    # 运行流水线并获取结果
    runner = LocalPipelineRunner()
    run_result = runner.run(test_pipeline())
    
    # 验证输出
    output_path = run_result.outputs['df_output'].path
    result_df = pd.read_csv(output_path)
    
    expected_df = pd.DataFrame(
        {
            'name': ['Ana', 'Maria', 'Josh'],
            'age': [30, 38, 44],
        }
    )
    assert result_df['age'].tolist() == expected_df['age'].tolist()

注意:此方法需避免pytest fixture与KFP组件装饰器的冲突,若fixture调用异常,可在测试函数内直接定义sample_df组件。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 09:05:23