跳转到主要内容

丰富的匹配器,适用于测试中的断言。受Hamcrest启发。

项目描述

Precisely 允许您编写精确的断言,以便您只需测试您真正感兴趣的行为。这使得读者更清楚地了解预期的行为,并使测试更不易破碎。这还允许在断言失败时生成更好的错误消息。受Hamcrest启发。

例如,假设我们想确保一个 唯一 函数从一个列表中删除重复项。我们可能会这样编写测试

from precisely import assert_that, contains_exactly

def test_unique_removes_duplicates():
    result = unique(["a", "a", "b", "a", "b"])
    assert_that(result, contains_exactly("a", "b"))

只要 result 包含 "a""b"(顺序不限),断言就会通过,但不会有其他项目。与 assert result == ["a", "b"] 不同,我们的断言忽略了元素顺序。这在以下情况下很有用:

  • 结果的顺序是非确定性的,例如使用 set

  • 唯一 的合同中未指定顺序。如果我们断言特定的顺序,那么我们就会测试实现而不是合同。

  • 唯一 的合同中指定了顺序,但顺序在单独的测试用例中进行了测试。

当断言失败时,错误信息不仅会说明两个值不相等,还会更详细地描述失败原因。例如,如果unique的值为["a", "a", "b"],我们会得到以下错误信息:

Expected: iterable containing in any order:
  * 'a'
  * 'b'
but: had extra elements:
  * 'a'

安装

pip install precisely

API

使用assert_that(value, matcher)来断言一个值是否满足匹配器。

许多匹配器是由其他匹配器组成的。如果它们接收到一个匹配器而不是一个值,那么这个值会被equal_to()包装。例如,has_attrs(name="bob")等价于has_attrs(name=equal_to("bob"))

  • equal_to(value):如果值等于value,则匹配该值,使用==进行比较。

  • has_attrs(**kwargs):如果值具有指定的属性,则匹配该值。例如

    assert_that(result, has_attrs(id=is_instance(int), name="bob"))
  • has_attr(attribute_name, matcher):如果值具有指定的属性,则匹配该值。通常情况下,当属性名称是常量时,使用has_attrs被认为是更符合语法的。例如,而不是

    assert_that(result, has_attr("id", is_instance(int)))

    使用

    assert_that(result, has_attrs(id=is_instance(int)))

  • contains_exactly(*args):如果可迭代对象包含相同的元素(顺序无关),则匹配该可迭代对象。例如

    assert_that(result, contains_exactly("a", "b"))
    # Matches ["a", "b"] and ["b", "a"],
    # but not ["a", "a", "b"] nor ["a"] nor ["a", "b", "c"]
  • is_sequence(*args):如果可迭代对象包含相同的元素(顺序相同),则匹配该可迭代对象。例如

    assert_that(result, is_sequence("a", "b"))
    # Matches ["a", "b"]
    # but not ["b", "a"] nor ["a", "b", "c"] nor ["c", "a", "b"]
  • includes(*args):如果可迭代对象包含所有指定的元素,则匹配该可迭代对象。例如

    assert_that(result, includes("a", "b"))
    # Matches ["a", "b"], ["b", "a"] and ["a", "c", "b"]
    # but not ["a", "c"] nor ["a"]
    assert_that(result, includes("a", "a"))
    # Matches ["a", "a"] and ["a", "a", "a"]
    # but not ["a"]
  • all_elements(matcher):如果可迭代对象中的每个元素都匹配matcher,则匹配该可迭代对象。例如

    assert_that(result, all_elements(equal_to(42)))
    # Matches [42], [42, 42, 42] and []
    # but not [42, 43]
  • is_mapping(matchers):如果映射(如dict)具有相同的键和匹配的值,则匹配该映射。如果映射缺少任何键或有多余的键,将引发错误。例如

    assert_that(result, is_mapping({
        "a": equal_to(1),
        "b": equal_to(4),
    }))
  • mapping_includes(matchers):如果映射(如dict)具有相同的键和匹配的值,则匹配该映射。如果映射缺少任何键,将引发错误,但允许有额外的键。例如

    assert_that(result, mapping_includes({
        "a": equal_to(1),
        "b": equal_to(4),
    }))
    # Matches {"a": 1, "b": 4} and {"a": 1, "b": 4, "c": 5}
    # but not {"a": 1} nor {"a": 1, "b": 5}
  • anything:匹配所有值。

  • is_instance(type):如果isinstance(value, type),则匹配任何值。

  • all_of(*matchers):如果所有子匹配器都匹配,则匹配该值。例如

    assert_that(result, all_of(
        is_instance(User),
        has_attrs(name="bob"),
    ))
  • any_of(*matchers):如果任何子匹配器匹配,则匹配该值。例如

    assert_that(result, any_of(
        equal_to("x=1, y=2"),
        equal_to("y=2, x=1"),
    ))
  • not_(matcher):否定匹配器。例如

    assert_that(result, not_(equal_to("hello")))
  • starts_with(prefix):如果字符串以prefix开头,则匹配该字符串。

  • contains_string(substring):如果字符串包含substring,则匹配该字符串。

  • greater_than(value):匹配大于value的值。

  • greater_than_or_equal_to(value):匹配大于或等于value的值。

  • less_than(value):匹配小于value的值。

  • less_than_or_equal_to(value):匹配小于或等于value的值。

  • close_to(value, delta):如果值在正负delta的容忍度范围内与value相近,则匹配该值。

  • has_feature(name, extract, matcher):如果extract(value)匹配matcher,则匹配value。例如

    assert_that(result, has_feature("len", len, equal_to(2)))

    为了清晰起见,通常将has_feature的使用提取到自己的函数中往往有帮助。

    def has_len(matcher):
        return has_feature("len", len, matcher)
    
    assert_that(result, has_len(equal_to(2)))
  • raises(matcher):如果value()抛出与matcher匹配的异常,则匹配value。例如

    assert_that(lambda: func("arg"), raises(is_instance(ValueError)))

替代方案

PyHamcrest是另一个Python匹配器的实现。我更喜欢该项目生成的错误消息,但你可以自由地自己判断

# Precisely
from precisely import assert_that, is_sequence, has_attrs

assert_that(
    [
        User("bob", "jim@example.com"),
        User("jim", "bob@example.com"),
    ],
    is_sequence(
        has_attrs(username="bob", email_address="bob@example.com"),
        has_attrs(username="jim", email_address="jim@example.com"),
    )
)

# Expected: iterable containing in order:
#   0: attributes:
#     * username: 'bob'
#     * email_address: 'bob@example.com'
#   1: attributes:
#     * username: 'jim'
#     * email_address: 'jim@example.com'
# but: element at index 0 mismatched:
#   * attribute email_address: was 'jim@example.com'

# Hamcrest
from hamcrest import assert_that, contains, has_properties

assert_that(
    [
        User("bob", "jim@example.com"),
        User("jim", "bob@example.com"),
    ],
    contains(
        has_properties(username="bob", email_address="bob@example.com"),
        has_properties(username="jim", email_address="jim@example.com"),
    )
)

# Hamcrest error:
# Expected: a sequence containing [(an object with a property 'username' matching 'bob' and an object with a property 'email_address' matching 'bob@example.com'), (an object with a property 'username' matching 'jim' and an object with a property 'email_address' matching 'jim@example.com')]
#      but: item 0: an object with a property 'email_address' matching 'bob@example.com' property 'email_address' was 'jim@example.com'

项目详情


下载文件

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

源分发

precisely-0.1.9.tar.gz (10.5 kB 查看哈希值)

上传时间

构建分发

precisely-0.1.9-py2.py3-none-any.whl (11.8 kB 查看哈希值)

上传时间 Python 2 Python 3

由以下组织支持