跳转到主要内容

合并合约和自动测试。

项目描述

icontract-hypothesis

Continuous integration Test coverage PyPI - version PyPI - Python Version

Icontract-hypothesis将设计-by-contract与自动测试相结合。

它是设计-by-contract库icontract和属性测试库Hypothesis之间的集成。

结果是强大的组合,允许您自动测试您的代码。您无需手动编写函数的Hypothesis搜索策略,icontract-hypothesis可以根据函数的前置条件推断它们。这使得自动测试变得非常简单。

您可以使用icontract-hypothesis

由于合约靠近代码,因此代码的演变也会自动演变测试。

用法

有两种方法可以将icontract-hypothesis作为库集成到您的测试中。

仅假设。 首先,您可以使用它来根据前置条件定义测试的假设

>>> from hypothesis import given
>>> import hypothesis.strategies as st

>>> import icontract
>>> import icontract_hypothesis

>>> @icontract.require(lambda x: x > 0)
... @icontract.ensure(lambda result: result > 0)
... def some_func(x: int) -> int:
...     return x - 1000

>>> assume_preconditions = icontract_hypothesis.make_assume_preconditions(
...     some_func)

>>> @given(x=st.integers())
... def test_some_func(x: int) -> None:
...    assume_preconditions(x)
...    some_func(x)

>>> test_some_func()
Traceback (most recent call last):
    ...
icontract.errors.ViolationError: File <doctest README.rst[4]>, line 2 in <module>:
result > 0:
result was -999
x was 1

icontract_hypothesis.make_assume_preconditions 创建的函数 assume_preconditions 将拒绝所有不满足 some_func 预条件的输入值。

推断策略。其次,您可以自动推断策略并测试函数。

>>> import icontract
>>> import icontract_hypothesis

>>> @icontract.require(lambda x: x > 0)
... @icontract.ensure(lambda result: result > 0)
... def some_func(x: int) -> int:
...     return x - 1000

>>> icontract_hypothesis.test_with_inferred_strategy(some_func)
Traceback (most recent call last):
    ...
icontract.errors.ViolationError: File <doctest README.rst[10]>, line 2 in <module>:
result > 0:
result was -999
x was 1

选择哪种方法取决于您如何编写测试。第一种方法,使用 assume_preconditions,如果您已经定义了搜索策略并且只想排除几个边缘情况,则非常实用。第二种方法,自动推断测试策略,如果您只想测试函数而不指定任何特定的搜索策略,则非常有用。

Icontract-hypothesis 保证推断的策略必须满足预条件。如果不满足,请考虑这是一个错误,在这种情况下,请创建一个问题,以便我们可以修复它。

如果您想检查策略或进一步程序化地改进它,请使用 icontract_hypothesis.infer_strategy

>>> import math

>>> import icontract
>>> import icontract_hypothesis

>>> @icontract.require(lambda x: x > 0)
... @icontract.require(lambda x: x > math.sqrt(x))
... def some_func(x: float) -> int:
...     pass

>>> icontract_hypothesis.infer_strategy(some_func)
fixed_dictionaries({'x': floats(min_value=0, exclude_min=True).filter(lambda x: x > math.sqrt(x))})

测试工具

我们提供了一个命令行工具 pyicontract-hypothesis test,您可以使用它来自动测试一个模块。

usage: pyicontract-hypothesis test [-h] -p PATH
                                   [--settings [SETTINGS [SETTINGS ...]]]
                                   [--inspect] [-i [INCLUDE [INCLUDE ...]]]
                                   [-e [EXCLUDE [EXCLUDE ...]]]

optional arguments:
  -h, --help            show this help message and exit
  -p PATH, --path PATH  Path to the Python file to test
  --settings [SETTINGS [SETTINGS ...]]
                        Specify settings for Hypothesis

                        The settings are assigned by '='.
                        The value of the setting needs to be encoded as JSON.

                        Example: max_examples=500
  --inspect             Only show the strategy and the settings

                        No tests are executed.
  -i [INCLUDE [INCLUDE ...]], --include [INCLUDE [INCLUDE ...]]
                        Regular expressions, lines or line ranges of the functions to process

                        If a line or line range overlaps the body of a function,
                        the function is considered included.

                        Example 1: ^do_something.*$
                        Example 2: 3
                        Example 3: 34-65
  -e [EXCLUDE [EXCLUDE ...]], --exclude [EXCLUDE [EXCLUDE ...]]
                        Regular expressions, lines or line ranges of the functions to exclude

                        If a line or line range overlaps the body of a function,
                        the function is considered excluded.

                        Example 1: ^do_something.*$
                        Example 2: 3
                        Example 3: 34-65

请注意,如果可以传递当前光标位置和当前文件名,则 pyicontract-hypothesis test 可以轻松集成到您的 IDE 中。

自动写作工具

手动编写基于属性的测试是繁琐的,可以部分自动化。为此,我们实现了自动写作实用程序 pyicontract-hypothesis ghostwrite,它基于您手动细化的预条件生成一个初稿。

usage: pyicontract-hypothesis ghostwrite [-h] (-m MODULE | -p PATH)
                                         [-o OUTPUT] [--explicit] [--bare]
                                         [-i [INCLUDE [INCLUDE ...]]]
                                         [-e [EXCLUDE [EXCLUDE ...]]]

optional arguments:
  -h, --help            show this help message and exit
  -m MODULE, --module MODULE
                        Module to ghostwrite the unit tests for
  -p PATH, --path PATH  Path to the module to ghostwrite the unit tests for.

                        If the file represents a module reachable through
                        sys.path, use the qualified module name in
                        the unit test.

                        Otherwise, the module is represented as the stem
                        of the path with all non-identifier characters
                        replaced with an underscore ("_").
  -o OUTPUT, --output OUTPUT
                        Path to the file where the output should be written.

                        If '-', writes to STDOUT.
  --explicit            Write the inferred strategies explicitly

                        This is practical if you want to tune and
                        refine the strategies and just want to use
                        ghostwriting as a starting point.

                        Mind that pyicontract-hypothesis does not
                        automatically fix imports as this is
                        usually project-specific. You have to fix imports
                        manually after the ghostwriting.
  --bare                Print only the body of the tests and omit header/footer
                        (such as TestCase class or import statements).

                        This is useful when you only want to inspect a single test or
                        include a single test function in a custom test suite.
  -i [INCLUDE [INCLUDE ...]], --include [INCLUDE [INCLUDE ...]]
                        Regular expressions, lines or line ranges of the functions to process

                        If a line or line range overlaps the body of a function,
                        the function is considered included.

                        Example 1: ^do_something.*$
                        Example 2: 3
                        Example 3: 34-65
  -e [EXCLUDE [EXCLUDE ...]], --exclude [EXCLUDE [EXCLUDE ...]]
                        Regular expressions, lines or line ranges of the functions to exclude

                        If a line or line range overlaps the body of a function,
                        the function is considered excluded.

                        Example 1: ^do_something.*$
                        Example 2: 3
                        Example 3: 34-65

自动写作测试的示例可在:tests/pyicontract_hypothesis/samples 中找到

作为模块运行工具

如果您出于某种原因想以模块形式运行工具,只需调用即可。

usage: icontract_hypothesis [-h] {test,ghostwrite} ...

Combine property-based testing with contracts of a Python module.

positional arguments:
  {test,ghostwrite}  Commands
    test             Test the functions automatically by inferring search
                     strategies from preconditions
    ghostwrite       Ghostwrite the unit tests with inferred search strategies

optional arguments:
  -h, --help         show this help message and exit

安装

icontract-hypothesis 可在 PyPI 上找到,网址为 https://pypi.ac.cn/project/icontract-hypothesis,因此您可以使用 pip

pip3 install icontract-hypothesis

搜索策略

模糊测试的简单方法是从随机样本输入数据,根据预条件过滤它,并在运行后确保后置条件。但是,如果您的可接受输入值范围很窄,拒绝抽样会变得不切实际地慢。

例如,假设有一个预条件 5 < x < 10。对所有可能的整数 x 进行采样很少会命中预条件(如果有的话),从而浪费宝贵的计算时间。随着参数数量的增加,由于 维度诅咒,问题会加剧。

Icontract-hypothesis 尝试更智能地解决搜索策略

  • 预条件与常见的代码模式相匹配以定义策略。例如,5 < x < 10 给出搜索策略 hypothesis.strategies.integers(min=6, max=9)

    我们目前匹配所有可用的 Hypothesis 类型(intfloatdatetime.date 等)的界限。我们还在 str 参数上匹配正则表达式。

  • 无法匹配的预条件,但操作单个参数的预条件基于类型提示推断,并与 Hypothesis FilteredStrategy 组合。

  • 剩余的预条件通过在最终传递给函数的整个固定字典上过滤来强制执行。

有一个正在进行中的努力,将策略匹配代码移动到 Hypothesis 并进一步开发它以包括更多情况。请参阅 此 Hypothesis 问题

请注意,源代码的静态分析可能无法确定各种作用域中定义的所有名称,因为它们也可以动态注入(例如,设置 __globals__ 属性或 globals()[random.choice("abc")] = 1)。只要您不将复杂的动态技巧用于您的合约,icontract-hypothesis 的策略推理应该能正常工作。

Hypothesis 会自动构建复合输入参数(类、数据类、命名元组等)。如果您的类在其构造函数(__init__)中强制执行前置条件,请确保它继承自 icontract.DBC

这样,icontract-hypothesis 将使用 hypothesis.strategies.register_type_strategy 将您的类注册到 Hypothesis,并在构建其实例时考虑前置条件。

重要的是您不应该使用 hypothesis.strategies.builds 与在构造函数中使用合约的类,因为 builds 将忽略已注册的策略。您应该使用 hypothesis.strategies.from_type 代替。参见 这个 Hypothesis 问题的评论相应的答案

通常,构造函数的默认推断策略应该足够,尽管您当然不限于这些。您可以使用 hypothesis.strategies.register_type_strategy 注册自己的策略。Icontract-hypothesis 会尊重以前的注册,而不会覆盖它们。

IDE 插件

版本控制

我们遵循语义版本控制。版本X.Y.Z表示

  • X是主版本(不向后兼容),

  • Y是次版本(向后兼容),

  • Z是补丁版本(向后兼容的bug修复)。

项目详细信息


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

由以下机构支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页