无魔法的Python文献BDD断言
项目描述
ensure 是一组简单的断言辅助工具,允许您编写更易于表达、文献化、简洁和易于阅读的Python代码,用于验证条件。它受到 should.js、expect.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'>
将此运行时类型检查与Mypy和PEP 484/Python 3.5+中的类型提示的编译时检查进行比较。
动机和目标
许多BDD断言库都存在魔法过多或必须构造不容易解析为英语的语句的问题。“ensure”故意保持简单,以避免这两种问题。其源代码易于阅读和扩展。
还需要进一步改进由“ensure”引发的错误消息的可读性、信息性和一致性。展望未来,提取结构化错误信息的能力将是主要的发展重点。您将控制每个错误中呈现的信息量、错误抛出的上下文以及异常对象将具有的反射能力。
“ensure”的原始用途是作为API端点的I/O验证助手,其中客户端需要收到一个非常明确的错误消息,可能需要添加一些结构化信息(例如HTTP错误代码和对失败元素的机器可读引用),并且可能需要隐藏一些信息。为了进一步提高这一点,我们将致力于改进错误翻译、打包、消息格式化和模式验证助手。
链接
错误
请将错误、问题、功能请求等报告到GitHub。
许可协议
根据Apache License,版本2.0的条款许可。
项目详细信息
下载文件
下载适用于您的平台文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源代码分发
构建分发
ensure-1.0.4.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 41b9093d591b5c25ee3c65c36da153b89abfcb7acb21ecebf21944704934f9b0 |
|
MD5 | 2dd79de520f30bd813d25311025a4c9b |
|
BLAKE2b-256 | 14b43c6b1c6af40fbd60bd1b2f0f8b55a8cbbc0993d4019a428b99e86f7bdee9 |
ensure-1.0.4-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ad88628482eac9bf91dd147ae677f26aeac6c65e1c91a161a0306dbd83f6efd9 |
|
MD5 | 140ba458da898efe3c4d2d0813ba3377 |
|
BLAKE2b-256 | 5334076953d6007a5eeaa2fdd2f78c23bdf4ba1926f3cd334d59af34f354ecc6 |