跳至主要内容

将缓存断言数据以简化复杂可序列化数据的回归测试

项目描述

pytest_cache_assert

将缓存断言数据以简化复杂可序列化数据的回归测试

安装

poetry add pytest_assert_check --dev

用法

该软件包的主要用途是回归测试大型、可序列化的字典,例如来自开发中的API。

您可能有参数化的测试用例,需要断言创建的字典保持不变,但您不想手动生成预期的字段和值进行比较。相反,您可以捕获序列化数据的快照并缓存结果,然后在重复的测试运行中使用缓存的 数据来检查一致性。应将缓存的文件提交到版本控制,这可以作为非常有用的文档

此软件包可以最小化测试用例逻辑,同时提高回归测试的全面性

此项目受到了pytest-recording的优秀项目的极大启发

替代方案

  • pytest-recording:这是我最常用的包,强烈推荐用于记录和重放 外部 API通信,从而使单元测试中API请求只需进行一次(即从测试套件中调用GitHub API以记录API响应)
  • pytest-snapshot:我在发布了pytest_assert_cache的1.0.0版本后才发现了这个包。这个包可以通过用户指定的序列化器进行更灵活的配置,可能是一个很好的替代方案。请参阅他们的文档获取更多信息。
  • snapshottest:这是在发布了1.0.0版本后找到的另一个方案,可能对大多数用户来说都是一个很好的替代方案。
  • dirty-equals:广泛检查值(例如,assert result == {'counter': IsPositiveInt, ...}等),而不是逐个字段访问和检查,这使得编写测试更容易,错误输出更容易审查。
  • pytest-pinned:可以编写类似于assert result == pinned的断言,其中结果存储在JSON中。
  • touca:使用云服务实现的更通用的快照风格测试。

基本示例

您创建了一个名为package_a的新项目,包含一个文件package_a/source_file.py和测试文件tests/test_file.py

"""package_a/source_file.py"""

import sys
from datetime import datetime
from typing import Any, Dict, List, Optional

from beartype import beartype
from pydantic import BaseModel


class User(BaseModel):  # noqa: H601
    """Example from pydantic documentation."""

    id: int  # noqa: A003,VNE003
    name: str = 'John Doe'
    signup_ts: Optional[datetime] = None
    friends: List[int] = []


@beartype
def create_data(name: str) -> Dict:
    """Arbitrary function that returns a dictionary.

    This demonstration uses pydantic, but any dictionary can be tested!

    """
    return User(id=sys.maxsize, name=name).dict()
"""tests/test_file.py"""

import pytest

from package_a.source_file import create_data


@pytest.mark.parametrize('name', ['Test Name 1', 'Test Name 2'])
def test_create_data(name, assert_against_cache):
    """Basic test of create_data()."""
    result = create_data(name=name)

    # One could manually create the expected dictionary
    cache = {'id': 9223372036854775807, 'signup_ts': None, 'friends': [], 'name': name}
    assert result == cache
    # ----------------------------------------------------------------------------------
    # Or utilize the pytest_cache_assert fixture to compare against the last cached version
    assert_against_cache(result)

当第一次运行时,pytest_cache_assert会自动创建:为每个参数创建缓存resulttests/cache-assert/source_file/test_file/test_create_data-[Test Name 1].json(和test_create_data[Test Name 2].json)。以下为test_create_data-[Test Name 1].json的示例。

{
  "_info": [
    {
      "func_args": {
        "name": "Test Name 1"
      },
      "test_file": "test_readme.py",
      "test_name": "test_create_data"
    }
  ],
  "_json": {
    "friends": [],
    "id": 9223372036854775807,
    "name": "Test Name 1",
    "signup_ts": null
  }
}

必须将缓存的JSON文件提交到版本控制,如果需要,可以手动编辑或删除,以便在下次运行测试套件时重新生成。

更多示例

在您的缓存字典中,您可能有一些具有更复杂逻辑的变量值,例如日期、UUID等。这些可以选性地忽略、匹配为null或其他用户指定的检查。

"""tests/test_readme_more.py."""

from contextlib import suppress
from datetime import datetime, timedelta
from uuid import uuid4

import pytest

from pytest_cache_assert import AssertRule, Wild, check_suppress, check_type


def test_assert_against_cache_key_rules(assert_against_cache):
    """Demonstrate use of `assert_rules`."""
    now = datetime.now()  # noqa: DTZ005
    cached_data = {
        'date': str(now),
        'nested': {'uuid': str(uuid4())},
        'ignored': {'a': 1, 'b': 2},
    }
    test_data = {
        'date': str(now + timedelta(hours=3)),
        'nested': {'uuid': str(uuid4())},
        'ignored': {'recursively': {'a': {'b': {'c': 1}}}},
    }
    with suppress(AssertionError):
        # Ensures that the cache file has been created
        assert_against_cache(cached_data)

    assert_rules = [
        # To ignore values for 'ignored.a' and 'ignored.b', create a rule
        #   Here, we use the wildcard for dictionary keys
        AssertRule.build_re(pattern=['ignored', Wild.recur()], func=check_suppress),

        # Instead of suppressing, the type of data could be resolved and compared
        #   This is useful for datetime or UUID values where we expect variability
        AssertRule(pattern='date', func=check_type),
        AssertRule.build_re(pattern=['nested', 'uuid'], func=check_type),

        # Any "func" with arguments 'old' and 'new' can be used as a rule
    ]

    # Without assert rules, an AssertionError is raised
    with pytest.raises(AssertionError):
        assert_against_cache(test_data)
    # But, with the custom logic, the cache assertion check will succeed
    assert_against_cache(test_data, assert_rules=assert_rules)

更多示例

有关更多示例代码,请参阅scripts目录或tests

自定义(beta)

注意:此功能被视为beta版本,可能会有所更改。然而,我将尽我所能保持相同的接口。

对于2.0.0版本,pytest_cache_assert已被重构为更容易自定义,除了缓存目录的配置选项之外,还包括覆盖文件命名方式以及覆盖缓存测试数据的序列化和验证的方式。

有了这些配置选项,用户或第三方包可以替换默认包的行为,例如更改数据序列化的文件格式(yamljsonlines等)以及指定不同的序列化逻辑。所有配置选项都可以通过创建带有提供实现的cache_assert_config固定装置来获得。

  • 有关配置选项和更多信息,请参阅plugin.py中的AssertConfig
    • always_write:始终写入缓存的文件,以便可以在用户的VCS中检查差异。
    • cache_dir_rel_path:从tests/开始的字符串相对目录。默认解析为tests/assert-cache/
    • cache_store:用于管理缓存表示的可配置类。默认为本地JSON。
    • converters:注册处理未处理类型转换的函数,例如pandas DataFrame。
    • validator:用于识别和总结与缓存偏差的自定义验证器。
import pytest

from pytest_cache_assert.plugin import AssertConfig


@pytest.fixture(scope='module')
def cache_assert_config():
    return AssertConfig(cache_dir_rel_path='custom/cache/dir')

项目状态

请参阅Open Issues和/或CODE_TAG_SUMMARY。有关发布历史,请参阅CHANGELOG

计划中的全局配置选项

以下是一些未来选项的想法,目前尚未实现,但如果感兴趣的话,将来可能会实现

  • 计划中:提供类似于 pytest-recording 的 CLI 参数(request.config.getoption("--record-mode") or "none"),以便进行一次性的配置更改
  • 计划中:考虑使用过滤器防止机密信息被缓存:filter_headers=[['authorization', 'id'], ['authorization', 'cookies']](尽管,您应该使用 pre-commit 钩子并在传递给缓存之前格式化字典)
  • 待办事项:添加 Jest 关于最佳实践的提示 -- 将快照视为代码等。

贡献

我们欢迎提交拉取请求!为了使您的拉取请求顺利通过,我们建议您首先在 GitHub 上创建一个问题来讨论您的想法。有关开始使用代码库的资源,请参阅以下文档

行为准则

我们遵循 贡献者公约行为准则

开源状态

我们尽量合理地满足 "OpenSSF scorecard" 的多数方面,该 scorecard 来自 Open Source Insights

负责任地披露

如果您有任何安全问题要报告,请私下联系项目维护者。您可以通过 dev.act.kyle@gmail.com 联系我们。

许可证

许可证

项目详情


下载文件

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

源代码分发

pytest_cache_assert-4.0.0.tar.gz (21.4 kB 查看哈希)

上传时间 源代码

构建分发

pytest_cache_assert-4.0.0-py3-none-any.whl (24.2 kB 查看哈希)

上传时间 Python 3

支持者