假设(Python):省略论证

Hypothesis (Python): Omit argument

我有一个这样的函数(它实际上是一个 class,但考虑到 Python 的鸭子类型,这不相关):

def myfunc(a=None, b=None):
    <snip>

现在我想写一个假设检验,它总是提供 a,但有时只提供 b

我试过了

from hypothesis import given, strategies as strat

@given(a=strat.booleans())
@given(b=strat.integers(min_value=1) | strat.nothing())
def test_model_properties(self, **kwargs):
    myval = myfunc(**kwargs)
    <snip>

但似乎当它获得 strat.nothing() 时只是跳过该测试 运行(当我将其用作 b 的唯一策略时,我得到 hypothesis.errors.FailedHealthCheck: It looks like your strategy is filtering out a lot of data.)。

我怎么只能有时提供假设检验的论点?我是否需要编写两个测试,一个有 b,一个没有?

怎么样:

def myfunc(a=None, b=None):
    if b is None:
        b=strat.nothing() 
        # Or whatever you would like b to be when you don't supply an argument
    else:
        <snip>

所以你让 b 成为默认值(在本例中,None)触发 myfunc() 内的 'if' 条件,将其设置为其他值。

你的方法肯定会失败,因为,正如 hypothesis docs 暗示的那样

hypothesis.strategies.nothing()[source]

    This strategy never successfully draws a value and will always reject on an attempt to draw.

您不为 b 提供值的尝试将永远失败。

这个怎么样:

from hypothesis.strategies import tuples, integers, booleans, one_of
B = booleans()
I = integers(min_value=0, max_value=10)
one_of(tuples(B), tuples(B, I)).example()

经过一系列试验,它给了我 (True,)(False, 9)(False, 4)(True, 5)(False,) 等输出。

当然,您会使用 *args 而不是 **kwargs

让我走上正轨——关键词的选择需要有自己的策略。

使用标准词典

std = {'a': strat.booleans()}

和可选字典

opt = {
    'b': strat.integers(),
    'c': strat.integers(),
}

然后我可以使用 chained list comprehension 来获得所有可能的 "optional argument combinations":

# chain.from_iterable may or may not be faster; it doesn't matter for me.
optional = [combo
            for n in range(len(opt.items()))
            for combo in itertools.combinations(opt.items(), n+1)]

bc(b, c) 生成键值元组。

为了绘制一组值,我们需要获得其中一个选项,这可以通过 sampled_from(optional) 完成。对于得到的元组,除了std字典中的策略外,我们还必须从中提取策略。

strat.sampled_from(optional).flatmap(
    lambda x: strat.fixed_dictionaries(
        {**std, **dict(x)}
    )
)

这些都可以封装在一个函数里,姑且称之为valid_values()。如果在包装函数的签名中指定 *args**kwargs,则不能使用 @given(valid_values())

因此,test_model_properties(self, **kwargs) 变为 test_model_properties(self, kwargs)(您可以使用 @given(kwargs=valid_values()))- 通过调用字典 kwargs,其余功能保持不变。

注意:如果您想要没有可选参数的可能性,这将不包括空元组,但可以轻松地将其附加到 optional 列表。或者,使用 range(n+1) 而不是 combinations(..., n+1),因此包括长度 0.

看起来你想要 none() 而不是 nothing():

from hypothesis import given, strategies as strat

@given(a=strat.booleans(), b=strat.none() | strat.integers(min_value=1))
def test_model_properties(self, **kwargs):
    myval = myfunc(**kwargs)
    ...

这比生成字典以用作 **kwargs 更简单,而且效率也更高一些。 b 的策略顺序也很重要——将 none() 放在首位可确保最小示例为 a=False, b=None 而不是 a=False, b=1.

另请注意,与单次使用相比,多次应用 @given 效率非常低,实际上自版本 3.34.0 起已弃用。