ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Hypothesis API 风格指南:为属性测试库设计一致、易用的策略 API

Hypothesis API 风格指南:为属性测试库设计一致、易用的策略 API 测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载Hypothesis 是一个基于属性的 Python 测试库property-based testing library其核心价值在于让开发者用少量代码表达测试意图再由引擎自动生成大量样本并完成最小化。为了让不断增长的策略strategyAPI 保持一致的手感Hypothesis 团队维护了一份名为House API Style的内部风格指南也就是本仓库中的 guides/api-style.rst。它主要面向两类读者一是为 Hypothesis 贡献新策略的维护者二是开发第三方扩展如hypothesis.extra模块的库作者。读完本文你将理解 Hypothesis 策略 API 的设计原则、参数约定、延迟求值机制以及哪些历史 API 被视为违规样板需要规避。这份指南并非代码风格规范而是一份API 设计规范——它回答的是什么样的公开接口看起来像 Hypothesis 的接口这一问题。通用准则Hypothesis API 的整体气质原文档在General Guidelines一节中给出了五条贯穿所有 API 的顶层原则它们是理解后续所有细节的总纲extras 模块的一致性优先编写hypothesis.extra扩展时与 Hypothesis 自身的一致性要优先于与所集成第三方库的一致性。也就是说即便某个库如 numpy、django有自己的惯用风格一旦进入 Hypothesis 的扩展模块也要按 Hypothesis 的规矩来。公开 API 绝对禁止子类化用户不应通过继承SearchStrategy之类的方式来自定义行为扩展点必须是组合式的。不过分追求Pythonic如果某个 API 让普通 Python 用户觉得奇怪团队会尝试找出一个同样喜欢但没那么怪异的替代方案——Pythonic是参考项不是绝对标准。第三方依赖必须隔离在hypothesis.extra中任何引入第三方包依赖的代码都应放入hypothesis.extra模块核心库保持零重依赖。复杂度不能转嫁给用户一个易用的 API 比一个简单的实现更重要。即实现可以复杂接口必须简单。从源码结构可以印证第 4 条本仓库的 hypothesis/src/hypothesis/extra 目录下按django、pandas、numpy、lark、redis、pytz、dateutil等第三方库分别组织模块核心的hypothesis.strategies则完全不依赖这些库。策略的定位配方与取值范围的中间地带针对策略本身指南给出了三条设计要领策略函数应介于构建值的配方与合法值的取值范围之间。它既要能描述如何构造也要能表达什么样的值合法但不应越界去规定值的统计分布。参数只说明如何产生合法值不暗示统计性质。分布提示distribution hints不属于策略参数的职责。策略应尽量抹平底层类型的非均匀性。指南举例hypothesis.extra.numpy为 numpy 在 object 数组上的怪异行为做了大量 workaround让用户感知不到底层差异。默认行为应尽量开放策略默认应允许生成它能支持的任何样本。例外只有极少数几乎不会感兴趣的失败输入目前仅有两处st.text()默认排除非 UTF-8 字符以及 numpy 数组默认排除零维度或零长度边。而这些例外都必须让用户轻松地显式选择加入opting in should be trivial。参数处理验证、默认值与关键字专属参数处理是 Hypothesis 风格中最具辨识度的部分原文列了八条细则每一条都可以在源码中找到对应实现1. 尽可能彻底地验证参数参数必须被验证到最大程度非法参数应以InvalidArgument错误拒绝而不是让内部异常泄漏给用户。例如 integers() 的源码开头就是一连串校验def integers( min_value: int | None None, max_value: int | None None, ) - SearchStrategy[int]: check_valid_bound(min_value, min_value) check_valid_bound(max_value, max_value) check_valid_interval(min_value, max_value, min_value, max_value) if min_value is not None: if min_value ! int(min_value): raise InvalidArgument(...) ...同样lists() 在构造策略前会调用check_valid_sizes(min_size, max_size)和check_strategy(elements, elements)并对手工unique与unique_by冲突等情况显式抛出InvalidArgument。2. 大量使用默认参数只要一个参数有合理的默认值就应该给默认值。lists()的签名是典型代表min_size: int 0、max_size: int | None None、unique_byNone、uniqueFalse。3. 集合类型策略的元素策略参数不设默认值这是一个刻意的例外lists()、sets()、tuples()等的第一个位置参数elements是必填的没有默认值。理由很实际——元素策略没有合理的通用默认强制显式传入能让用户清楚地意识到自己在生成什么。4. 有默认值的参数应设为 keyword-only除min_value/max_value之外带默认值的参数都应是关键字专属参数。lists()的签名完美示范了这一点elements是唯一的位置参数其余全部是*之后的 keyword-only。floats()更彻底def floats( min_value: Real | None None, max_value: Real | None None, *, allow_nan: bool | None None, allow_infinity: bool | None None, allow_subnormal: bool | None None, width: Literal[16, 32, 64] 64, exclude_min: bool False, exclude_max: bool False, ) - SearchStrategy[float]:见 numbers.py5.min_value/max_value的默认规则及其例外对于无界类型如整数min_value/max_value默认None表示无界对于有界类型如 datetime默认应取最小/最大值。floats()是这条规则的显式例外因为浮点需要特殊处理无穷大infinity和 NaN源码中allow_nan的默认值由边界决定——allow_nan bool(min_value is None and max_value is None)且显式设置allow_nanTrue的同时给出边界会直接抛InvalidArgument。6. 交互式参数默认行为应自动调整当参数之间存在约束关系如必须有序、至多一个合法、一个参数限制另一个的范围时默认值的行为必须随之自动调整。典型例子是floats()allow_nan与边界参数交互、allow_infinity与双边界交互源码都做了自动推导与冲突校验。7. 实际默认值依赖其他参数时默认参数应为 None如果某个参数的最终生效值取决于其它参数那么在签名里应写None由函数体去推导真正使用的值。floats()的allow_nan、allow_infinity、allow_subnormal全部遵循此模式。8. 参数顺序与值 vs 策略的决策前一到两个参数最可能被位置传参因此应把最常用、最自然的值放在前面。集合类型的elements放第一位、有序类型的min_value/max_value放前两位都遵循这一原则。考虑用户是否想让该参数经常变化如果用户很可能写some_strategy.flatmap(lambda x: my_new_strategy(argumentx))那么这个参数就应该直接接收一个策略而不是一个值。禁止值或策略二选一的参数如果你倾向于写传值或传生成该值的策略请改成只接收策略用户想传固定值时用st.just(value)包一层即可。最后一条来自原文档的警告值得单独强调当参数组合导致无法生成任何东西时应raise InvalidArgument而不是返回nothing()。返回空策略null strategy在概念上很优雅但在组合策略中会导致部分被静默丢弃从而产生意外地弱化的测试。函数与参数命名约定命名方面原文坦诚没有真正的一致性但给出了大致方向函数名遵循 Python 标准的snake_case。针对特定类型的策略通常以该类型的复数形式命名当类型本身有截断形式如int、str时策略名使用更长的完整形式。这正是integers()、text()而非ints()、str()的原因——从 hypothesis/src/hypothesis/strategies/init.py 的公开导出列表可以清楚看到这套命名体系。其余策略没有统一的命名惯例。参数命名则有两条硬性约定要求跨策略保持一致集合类型元素策略永远放在最前面单一元素策略必须叫elementsdictionaries()用keys/values是允许的例外因为有两个元素策略。见lists(elements, ...)、sets(elements, ...)的签名。有序类型前两个参数必须是下界和上界命名为min_value和max_value。这是它们作为唯一带默认值却仍可位置传参例外的根本原因。集合大小集合类型必须有min_size/max_size控制尺寸范围且min_size默认0、max_size默认None即使内部实际有界签名上也写None。延迟错误把错误推迟到测试运行时延迟错误Deferred Errors是 Hypothesis API 风格中最深刻的一条设计哲学原文用相当篇幅阐述了它。机制错误应在测试运行时抛出而非定义时尽可能让函数在测试运行时典型实现方式是推迟到从策略中draw时才抛出报错而不是在策略被调用时就报错。这主要适用于策略函数以及given自身的一部分错误条件。原文档指出这一机制通常由defines_strategy装饰器自动完成。查看源码 hypothesis/src/hypothesis/strategies/_internal/utils.py可以看到它正是延迟求值的实现核心def defines_strategy( *, force_reusable_values: bool False, eager: bool | Literal[try] False, ) - Callable[[T], T]: ... proxies(strategy_definition) def accept(*args, **kwargs): from hypothesis.strategies._internal.lazy import LazyStrategy if eager try: try: return strategy_definition(*args, **kwargs) except Exception: pass result LazyStrategy(strategy_definition, args, kwargs) ...装饰器默认把策略函数包装进LazyStrategy即调用策略函数时不真正求值只在测试中首次绘制draw样本时才执行定义函数。eagertry模式会先尝试立即求值一次一旦抛异常就回退到懒包装从而把错误原样推迟到测试运行时。为什么要这样做原文给出三点核心理由导入期错误难以调试测试代码在导入阶段报错会让人措手不及。用户天然期望测试代码的错误表现为测试失败即使这段代码写在装饰器里这种期望也不应被打破。运行时错误定位更好弃用警告deprecation warning等提示在测试内部发生时能更好地与具体测试绑定——测试运行器常常吞掉导入期的输出或把它放到奇怪的位置。一致性使用data交互式绘制、flatmap链式组合、composite组合策略时策略只有在测试运行阶段才会被求值错误只能发生在那里。如果有时定义时报错、有时测试时报错会非常诡异。一个明确的例外指南明确说明目前没有为错误调用函数如非法关键字参数、缺少必填参数导致的TypeError做延迟化。理论上可以但那样会让函数签名难以阅读等于用一种可理解性换另一种可理解性至今被认为不值得。第三方策略作者须知值得注意defines_strategy的文档字符串明确写道——第三方策略库作者不需要使用该装饰器它是 Hypothesis 内部机制仅用于把策略注册进_all_strategies全局注册表供文档完备性检查等内部测试使用。第三方库若想享受延迟求值可自行参考LazyStrategy的实现模式位于 hypothesis/src/hypothesis/strategies/_internal/lazy.py。从规范推断策略from_*家族的约定从某个规格或模式specification/schema推断策略的函数对用户非常方便同时让合法输入和实际测试的输入有单一事实来源。约定如下命名这类函数应命名为from_foo()第一个参数是被推断的对象。本仓库中典型成员包括st.from_type()、st.from_regex()、extra.lark.from_lark()、extra.numpy.from_dtype()。其余参数一律是可选的 keyword-only。局部定制路径要平滑用户不应因为需要一点定制就从零开始。指南表扬from_dtype()是范例查看 hypothesis/src/hypothesis/extra/numpy.py其签名在dtype之后提供了alphabet、min_size、max_size、min_value、max_value、allow_nan、allow_infinity等一整套 keyword-only 覆盖参数兼容的参数会被透传给被推断的策略函数不适用的被忽略从而平滑地定制推断结果的任意局部。repr 应可读在可行时返回策略的repr应展示其构造方式例如repr(from_type(int)) integers()除非必要才使用st.composite。作为补充from_type() 的源码文档展示了类型推断的完整查找顺序是理解从规范推断的最佳例证默认查找表或用户注册表中命中对应策略typing模块的类型走特殊逻辑存在子类型时返回各子类型策略的并集类型的所有必需参数都有注解且非抽象类时通过st.builds()解析抽象类型按具体子类的并集处理注意基于继承而非ABCMeta.register。用户可以用st.register_type_strategy()注册自定义类型例如全局排除 NaN、改用带时区的 datetime 策略等。当前违规清单历史包袱与改进方向指南最后诚实列出了当前与上述风格不一致的地方这些是未来弃用deprecation和改进的候选目标hypothesis.extra.numpy部分参数值或策略二选一——直接违反参数不应是值或策略的规则。hypothesis.extra.numpy假设数组定长——没有min_size/max_size参数。但原文也承认这很可能没问题因为数组形状更复杂。hypothesis.stateful是基于子类化的烂摊子——原文用 a great big subclassing based train wreck 形容直接违反公开 API 禁止子类化的准则是需要重点重构的历史区域。这段自述说明风格指南是规范性的normative而非描述性的descriptive。旧 API 可能与指南不一致尤其早期策略团队也做过失败的实验当与向后兼容冲突时向后兼容远比风格一致重要。这是阅读和使用该指南时最重要的心法。结语如何应用这份风格指南对 Hypothesis 贡献者而言这份指南是提交新策略前必须对照的检查清单参数是否充分验证、默认值是否合理、带默认的参数是否 keyword-only、元素策略是否置于首位、错误是否被推迟到测试运行时、命名是否符合from_*/复数命名惯例、是否避免值或策略二合一参数、无法生成时是否抛InvalidArgument而非返回nothing()。对第三方扩展作者而言指南同样适用且被明确鼓励遵循原文档也欢迎在指南不契合自身领域时联系社区讨论修改。写出的策略 API 若能看起来就像 Hypothesis 自己的 API用户的测试体验会高度一致——而这正是这份 House API Style 存在的全部意义。进一步阅读建议策略的公开入口与导出清单hypothesis/src/hypothesis/strategies/init.pydefines_strategy与策略缓存实现hypothesis/src/hypothesis/strategies/_internal/utils.pyintegers()/floats()的参数校验范例hypothesis/src/hypothesis/strategies/_internal/numbers.pylists()/from_type()等核心策略hypothesis/src/hypothesis/strategies/_internal/core.pyfrom_dtype()的平滑定制范例hypothesis/src/hypothesis/extra/numpy.py类型推断的测试覆盖hypothesis/tests/cover/test_type_lookup.py赞分享测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载相关推荐Hypothesis项目API设计风格指南Hypothesis项目API设计风格指南 概述 Hypothesis作为一个基于属性的测试框架其API设计风格直接影响着用户的使用体验。本文将深入解析Hyp测试开发工具Ornith-1.0-9B-bf16高级技巧温度参数调优与最大令牌设置指南Ornith 1.0 9B bf16高级技巧温度参数调优与最大令牌设置指南 想要充分发挥Ornith 1.0 9B bf16大语言模型的潜力吗掌握温度参数调高效管理学术文献的3个实用方法Zotero Style插件完全指南高效管理学术文献的3个实用方法Zotero Style插件完全指南 Zotero Style是一款强大的Zotero插件专为学术研究人员和文献管理者设计提桌面应用知识管理科研上一篇Bonsai-8B-mlx-1bit与GGUF Q1_0_g128格式对比哪个更适合你下一篇如何3分钟上手智能爬虫告别代码的无代码采集工具全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表