如何为 Plotly Dash 回调函数编写测试用例?

Kit*_*Law 6 python pytest plotly-dash

我正在尝试在页面中为 Dash 回调函数编写一个测试用例,该页面将 2 个数据表的选定行存储到dash_core_components.Store,并通过以下方式确定状态dash.callback_context

示例回调函数在index.py

@app.callback(
    Output('rows-store', 'data'),
    Input('datatable1', 'selected_rows'),
    Input('datatable2', 'selected_rows'),
)
def store_row_selection(row1: list, row2: list) -> dict:
    ctx = dash.callback_context
    if not ctx.triggered:
        return {'row-index-v1': [0], 'row-index-v2': [0]}
    else:
        return {'row-index-v1': row1, 'row-index-v2': row2}
Run Code Online (Sandbox Code Playgroud)

示例测试用例位于test.py

def test_store_row_selection_1(app_datatable):
    r1 = [0]
    r2 = [1]
    result = store_row_selection(r1, r2)

    assert type(result) is dict  
Run Code Online (Sandbox Code Playgroud)

但是,Pytest 在运行时抛出异常,测试回调函数的正确方法是什么以及如何让它工作?

  @wraps(func)
    def add_context(*args, **kwargs):
>       output_spec = kwargs.pop("outputs_list")
E       KeyError: 'outputs_list'
Run Code Online (Sandbox Code Playgroud)

Bas*_*den 8

我建议您测试未修饰的函数@app.callback

Dash 用于functools.wraps创建回调(源代码)。这意味着我们可以访问__wrapped__函数的属性store_row_selection

__wrapped__属性为我们提供了原始的底层函数,store_row_selection在本例中:

def test_store_row_selection_1():
    r1 = [0]
    r2 = [1]
    result = store_row_selection.__wrapped__(r1, r2)

    assert type(result) is dict
Run Code Online (Sandbox Code Playgroud)

另一种方法是将回调代码分成几部分:

def do_stuff(row1, row2):
    # Do things
    return {"row-index-v1": row1, "row-index-v2": row2}


@app.callback(
    Output("rows-store", "data"),
    Input("datatable1", "selected_rows"),
    Input("datatable2", "selected_rows"),
)
def store_row_selection(row1: list, row2: list) -> dict:
    return do_stuff(row1, row2)
Run Code Online (Sandbox Code Playgroud)

并测试不依赖于 Dash 回调的函数(do_stuff在本例中)。

根据编辑和评论进行更新

我可以知道是否也可以在 pytest 中触发 ctx.triggered 吗?

dash.call_context仅在回调(source)内可用,因此该__wrapped__方法在这种情况下不起作用。另一种方法(拆分函数)可以工作,因为您可以传递ctx到测试do_stuffcallback_context在测试中进行模拟。