使用 pytest 和假设进行异常处理和测试

suv*_*ayu 4 python expected-exception pytest python-hypothesis

我正在编写带有假设的统计分析测试。ZeroDivisionError当传递非常稀疏的数据时,假设导致我在代码中出现 a 。所以我调整了我的代码来处理异常;就我而言,这意味着记录原因并重​​新引发异常。

try:
    val = calc(data)
except ZeroDivisionError:
    logger.error(f"check data: {data}, too sparse")
    raise
Run Code Online (Sandbox Code Playgroud)

我需要通过调用堆栈向上传递异常,因为顶级调用者需要知道存在异常,以便它可以将错误代码传递给外部调用者(REST API 请求)。

编辑:我也无法为val;分配合理的值 本质上我需要一个直方图,当我根据数据计算合理的箱宽度时会发生这种情况。显然,当数据稀疏时,这会失败。如果没有直方图,算法就无法继续进行。

现在我的问题是,在我的测试中,当我做这样的事情时:

@given(dataframe)
def test_my_calc(df):
    # code that executes the above code path
Run Code Online (Sandbox Code Playgroud)

hypothesis不断生成触发的失败示例ZeroDivisionError,并且我不知道如何忽略此异常。通常我会用 标记这样的测试pytest.mark.xfail(raises=ZeroDivisionError),但在这里我不能这样做,因为相同的测试可以通过良好的输入。

像这样的东西是理想的:

  1. 但是,对大多数输入照常继续测试
  2. ZeroDivisionError引发时,将其视为预期失败而跳过。

我怎样才能做到这一点?我还需要try: ... except: ...在测试主体中放入 a 吗?我需要在 except 块中做什么才能将其标记为预期失败?

编辑:为了解决@hoefling的评论,分离出失败的案例将是理想的解决方案。但不幸的是,hypothesis没有给我足够的句柄来控制它。我最多可以控制生成数据的总数和限制(最小值,最大值)。然而,失败案例的范围非常窄。我没有办法控制这一点。我想这就是假设的要点,也许我根本不应该为此使用假设。

这是我生成数据的方式(稍微简化):

cities = [f"city{i}" for i in range(4)]
cats = [f"cat{i}" for i in range(4)]


@st.composite
def dataframe(draw):
    data_st = st.floats(min_value=0.01, max_value=50)
    df = []
    for city, cat in product(cities, cats):
        cols = [
            column("city", elements=st.just(city)),
            column("category", elements=st.just(cat)),
            column("metric", elements=data_st, fill=st.nothing()),
        ]
        _df = draw(data_frames(cols, index=range_indexes(min_size=2)))
        # my attempt to control the spread
        assume(np.var(_df["metric"]) >= 0.01)
        df += [_df]
    df = pd.concat(df, axis=0).set_index(["city", "category"])
    return df
Run Code Online (Sandbox Code Playgroud)

Zac*_*dds 6

from hypothesis import assume, given, strategies as st

@given(...)
def test_stuff(inputs):
    try:
        ...
    except ZeroDivisionError:
        assume(False)
Run Code Online (Sandbox Code Playgroud)

assume调用将告诉 Hypothesis 该示例“不好”,它应该尝试另一个示例,而不会导致测试失败。.filter(will_not_cause_zero_division)如果你有这样的函数的 话,这相当于调用你的策略。有关详细信息,请参阅文档。