跳转到主要内容

无魔法的Python文献BDD断言

项目描述

ensure 是一组简单的断言辅助工具,允许您编写更易于表达、文献化、简洁和易于阅读的Python代码,用于验证条件。它受到 should.jsexpect.js 的启发,并建立在 unittest/JUnit断言辅助工具 之上。

如果您使用Python 3,可以使用 ensure 来强制执行您的 函数签名注解:请参阅 PEP 3107 和下面的 @ensure_annotations装饰器。

由于 ensure 运行速度快,是一个独立的库(不是测试框架的一部分),不进行猴子补丁或使用DSL,也不使用断言语句(可能会因为 -O 标志而关闭),因此它可以用于验证生产代码中的条件,而不仅仅是用于测试(尽管它当然可以作为BDD测试实用程序库使用)。

除了代码外观更美观之外,使用 ensure 的另一个重要原因是它提供了更一致、更易读、更富有信息性的错误消息,当发生错误时。有关更多信息,请参阅 动机和目标

安装

pip install ensure

概述

from ensure import ensure

ensure(1).is_an(int)
ensure({1: {2: 3}}).equals({1: {2: 3}}).also.contains(1)
ensure({1: "a"}).has_key(1).whose_value.has_length(1)
ensure.each_of([{1: 2}, {3: 4}]).is_a(dict).of(int).to(int)
ensure(int).called_with("1100101", base=2).returns(101)
ensure(dict).called_with(1, 2).raises(TypeError)
check(1).is_a(float).or_raise(Exception, "An error happened: {msg}. See http://example.com for more information.")

在Python 3中

from ensure import ensure_annotations

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

请参阅下面的 更多示例

注意

“ensure”模块导出“Ensure”类及其便利实例“ensure”。该类的实例是可调用的,调用会重置实例正在检查的内容,因此可以多次用于检查(如上所示)。

默认情况下,该类会抛出“EnsureError”(“AssertionError”的子类)。

根据语法上下文,有多种方式可以“链式调用子句”:下面是关于“.also”、《.which》和“.whose_value”的示例。

抛出自定义异常

您可以将可调用对象或异常类作为关键字参数“error_factory”传递给“Ensure()”,或者您可以使用“Check”类或其便利实例“check()”。此类的行为类似于“Ensure”,但不会立即引发错误。它将错误保存下来,并将方法“otherwise()”、“or_raise()”和“or_call()”链接到子句的末尾。

from ensure import check

check("w00t").is_an(int).or_raise(Exception)
check(1).is_a(float).or_raise(Exception, "An error happened: {msg}. See http://example.com for more information.")
check("w00t").is_an(int).or_raise(MyException, 1, 2, x=3, y=4)
def build_fancy_exception(original_exception):
    return MyException(original_exception)

check("w00t").is_an(int).otherwise(build_fancy_exception)
check("w00t").is_an(int).or_call(build_fancy_exception, *args, **kwargs)

更多示例

ensure({1: {2: 3}}).is_not_equal_to({1: {2: 4}})
ensure(True).does_not_equal(False)
ensure(1).is_in(range(10))
ensure(True).is_a(bool)
ensure(True).is_(True)
ensure(True).is_not(False)
ensure(["train", "boat"]).contains_one_of(["train"])
ensure(range(8)).contains(5)
ensure(["spam"]).contains_none_of(["eggs", "ham"])
ensure("abcdef").contains_some_of("abcxyz")
ensure("abcdef").contains_one_or_more_of("abcxyz")
ensure("abcdef").contains_all_of("acf")
ensure("abcd").contains_only("dcba")
ensure("abc").does_not_contain("xyz")
ensure([1, 2, 3]).contains_no(float)
ensure(1).is_in(range(10))
ensure("z").is_not_in("abc")
ensure(None).is_not_in([])
ensure(dict).has_attribute('__contains__').which.is_callable()
ensure({1: "a", 2: "b", 3: "c"}).has_keys([1, 2])
ensure({1: "a", 2: "b"}).has_only_keys([1, 2])
ensure(1).is_true()
ensure(0).is_false()
ensure(None).is_none()
ensure(1).is_not_none()
ensure("").is_empty()
ensure([1, 2]).is_nonempty().also.has_length(2)
ensure(1.1).is_a(float).which.equals(1.10)
ensure(KeyError()).is_an(Exception)
ensure({x: str(x) for x in range(5)}).is_a_nonempty(dict).of(int).to(str)
ensure({}).is_an_empty(dict)
ensure(None).is_not_a(list)
import re
ensure("abc").matches("A", flags=re.IGNORECASE)
ensure([1, 2, 3]).is_an_iterable_of(int)
ensure([1, 2, 3]).is_a_list_of(int)
ensure({1, 2, 3}).is_a_set_of(int)
ensure({1: 2, 3: 4}).is_a_mapping_of(int).to(int)
ensure({1: 2, 3: 4}).is_a_dict_of(int).to(int)
ensure({1: 2, 3: 4}).is_a(dict).of(int).to(int)
ensure(10**100).is_numeric()
ensure(lambda: 1).is_callable()
ensure("abc").has_length(3)
ensure("abc").has_length(min=3, max=8)
ensure(1).is_greater_than(0)
ensure(1).exceeds(0)
ensure(0).is_less_than(1)
ensure(1).is_greater_than_or_equal_to(1)
ensure(0).is_less_than_or_equal_to(0)
ensure(1).is_positive()
ensure(1.1).is_a_positive(float)
ensure(-1).is_negative()
ensure(-1).is_a_negative(int)
ensure(0).is_nonnegative()
ensure(0).is_a_nonnegative(int)
ensure([1,2,3]).is_sorted()
ensure("{x} {y}".format).called_with(x=1, y=2).equals("1 2")
ensure(int).called_with("1100101", base=2).returns(101)
ensure("{x} {y}".format).with_args(x=1, y=2).is_a(str)
with ensure().raises(ZeroDivisionError):
    1/0
with ensure().raises_regex(NameError, "'w00t' is not defined"):
    w00t

请参阅完整的API文档

强制函数注释

使用装饰器@ensure_annotations来强制执行函数签名注释

from ensure import ensure_annotations

@ensure_annotations
def f(x: int, y: float) -> float:
    return x+y

f(1, 2.3)
>>> 3.3
f(1, 2)
>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>

将此运行时类型检查与MypyPEP 484/Python 3.5+中的类型提示的编译时检查进行比较。

动机和目标

许多BDD断言库都存在魔法过多或必须构造不容易解析为英语的语句的问题。“ensure”故意保持简单,以避免这两种问题。其源代码易于阅读和扩展。

还需要进一步改进由“ensure”引发的错误消息的可读性、信息性和一致性。展望未来,提取结构化错误信息的能力将是主要的发展重点。您将控制每个错误中呈现的信息量、错误抛出的上下文以及异常对象将具有的反射能力。

“ensure”的原始用途是作为API端点的I/O验证助手,其中客户端需要收到一个非常明确的错误消息,可能需要添加一些结构化信息(例如HTTP错误代码和对失败元素的机器可读引用),并且可能需要隐藏一些信息。为了进一步提高这一点,我们将致力于改进错误翻译、打包、消息格式化和模式验证助手。

作者

  • Andrey Kislyuk

  • Harrison Metzger

许可协议

根据Apache License,版本2.0的条款许可。

https://github.com/kislyuk/ensure/workflows/CI/badge.svg https://codecov.io/github/kislyuk/ensure/coverage.svg?branch=master https://img.shields.io/pypi/v/ensure.svg https://img.shields.io/pypi/l/ensure.svg

项目详细信息


下载文件

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

源代码分发

ensure-1.0.4.tar.gz (21.5 kB 查看哈希值)

上传时间 源代码

构建分发

ensure-1.0.4-py3-none-any.whl (15.6 kB 查看哈希值)

上传时间 Python 3

由以下支持

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