组合式收缩(Compositional Shrinking):Hypothesis 如何通过“收缩输入而非输出“让策略组合保留收缩能力
测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载导读本文是 Hypothesis 官方博客收缩shrinking系列的第二篇深入剖析属性测试中收缩shrinking机制的两种经典实现路径以 theft、QuickTheories 为代表的值驱动收缩value-based shrinking以及以 clojure.test.check 的玫瑰树rose tree和 Hypothesis 的统一中间表示为代表的输入驱动收缩input-based shrinking。读完本文你将理解为什么integers().map(lambda x: x * 2)这种最简单的策略组合在值驱动收缩下无法保留收缩能力而 Hypothesis 通过收缩输入等价于收缩输出的核心思想让任意策略组合都能自动获得正确、保不变的收缩结果——这正是现代属性测试库永远不需要用户手写 shrinker的根本原因。背景从类型驱动收缩到值驱动收缩在上一篇《集成式收缩Integrated shrinking》中原文档见 website/content/2016-12-05-integrated-shrinking.md我们讨论了以 Haskell QuickCheck 为代表的**类型驱动收缩type based shrinking**的根本缺陷收缩行为由值的类型决定与生成过程无关。这会导致生成器和收缩器各说各话——例如integers().map(lambda x: x * 2)生成的全是偶数但基于类型的收缩器会认为1 也是合法的整数而把失败的测试用例缩到 1从而把n 4的失败缩成n % 2 0的失败得到完全误导性的最小反例。当时文章忽略了一个折中方案halfway house值驱动收缩value-based shrinking。它虽然不如类型驱动那么糟但依然存在显著问题。这种方案在若干实现中可见——其代表性库包括 theft 和 QuickTheories。值驱动收缩不再依据类型而是采用经典的收缩 API接收一个值返回该值所有可能收缩结果的惰性列表lazy list。用户或库作者为每个可收缩的东西定义一个函数value - [smaller values]。这种方案解决了类型驱动收缩的主要问题收缩不再与生成过程脱节但仍然比较脆弱并且——关键的一点——它的组合性远不如 Hypothesis 或 test.check 所采用的方法。核心论点收缩不应基于任何具体值作者在文中明确提出理想形态除了不基于被生成值的类型之外收缩也不应该基于实际生成出来的值。乍看反直觉不基于值还能基于什么但这在实践中效果很好。要理解这一点需要先看值驱动方案在组合上的致命伤。经典反例map 组合下无法定义收缩考虑上一篇中的例子from hypothesis import given from hypothesis.strategies import integers even_numbers integers().map(lambda x: x * 2) given(even_numbers) def test_even_numbers_are_even(n): assert n % 2 0这里我们拿到一个策略用map函数把它组合成一个新策略。假设 Hypothesis 的策略实现早期确实如此长这样class SearchStrategy: def generate(self, random): raise NotImplementedError() def shrink(self, value): return ()也就是说我们能生成一个值也能收缩一个已经生成的值。默认情况下子类不知道如何生成必须实现generate也收缩不了任何东西除非子类自己提供shrink。这正是 theft 或 QuickTheories 采取的路线。问题在于在这个模型下上面用到的map不可能在保留收缩的前提下实现。要收缩map生成的值你必须能求逆你正在组合的那个函数——把生成值映射回产生它的原始值收缩原始值再经过映射函数得到收缩后的输出。而在一般情况下函数的求逆是不可能的即使你的语言真的提供了某种求逆工具绝大多数语言也没有。Hypothesis 和 test.check 都支持远比map复杂的策略组合两者的操作集合相同但 Hypothesis 的底层实现在更复杂的组合上表现更好例如 Hypothesis 支持flatmap、composite装饰器、deferred递归策略等。但即使是最简单的map组合只要需要收缩任意值就会失败。关键洞见收缩输出几乎总是等价于收缩输入为了收缩输出几乎总是只要收缩输入就够了。理论上确实存在更简单的输入导致更复杂的输出的函数但在实践中这种情形足够罕见以至于完全可以接受在这些情况下测试输出稍复杂一点。于是收缩map策略输出值的方法变得极其简单直接收缩第一个策略生成的原始值然后把结果喂给映射函数即可。这要求底层 API 必须支持这种作用于生成过程而非最终值的收缩方式。两条实现路径路径一test.check 的玫瑰树Rose Treetest.check 的做法是不再生成单个值而是生成一整棵**惰性的值树**树中包含每个值的收缩候选。这就是所谓的玫瑰树rose tree其每个节点既是值又是其收缩子树。Reid Draper 在Writing a simple property-based library一文中有更详细的论述。这种结构让map变得非常容易只需把映射函数应用到整棵玫瑰树上——既作用于初始生成的值也作用于所有收缩后的子值。换言之map从值→值的变换升级为树→树的变换收缩关系被结构性保留。路径二Hypothesis 的统一中间表示Unified IRHypothesis 的实现更复杂原文表示将留待后续文章详述但核心思想是把收缩输出等价于收缩输入推演到逻辑终点Hypothesis 拥有一个统一的中间表示IR所有生成都基于它。现代 Hypothesis 源码正是这样组织的整个hypothesis/internal/conjecture/目录就是围绕这份 IR 构建的。关键在于策略只是一个函数它接收一个 IR 对象并返回一个值。MappedStrategy天然免费获得收缩它做同样的事情——内部先从mapped_strategy画出一个值再应用pack映射函数见 strategies.py 中MappedStrategy.do_draw的实现def do_draw(self, data: ConjectureData) - MappedTo: ... for _ in range(3): try: data.start_span(MAPPED_SEARCH_STRATEGY_DO_DRAW_LABEL) x data.draw(self.mapped_strategy) result self.pack(x) data.stop_span() ... return result except UnsatisfiedAssumption as err: ...可以看到map的底层实现就是从底层策略画一个值调用pack(x)——没有定义任何针对映射后值的手写收缩逻辑。收缩发生在 IRchoice 序列层面与具体值无关。策略可以提供可能有用的收缩提示但对收缩过程本身几乎没有控制权。也就是说收缩器shrinker工作在选择序列choice sequence之上而不是在用户可见的值之上。这可以从 shrinking 子模块的目录结构 看出integer.py、floats.py、collection.py、ordering.py、string.py、bytes.py等全部是针对中间表示片段整数选择、浮点选择、集合选择、顺序选择……的局部收缩器而非针对值类型的收缩器。这一设计带来最直接的好处map之后连_invert都不需要。当前仓库中的 MappedStrategy._invert 只有在 pack 是 dict 类构造器这类极少数可逆情形下才尝试求逆否则直接抛CannotInvert——因为收缩根本不需要求逆shrink 阶段是拿更小的选择序列去重放replay生成过程而不是拿更小的值去逆推。从源码看 IR 收缩的实际调用链当前实现中一次完整的生成 收缩的简化调用链是生成ConjectureData通过data.draw(strategy)驱动策略的do_draw(data)策略内部通过data.draw_integer(...)、data.draw_boolean(...)等原语调用把每一次选择追加到data.nodeschoice 序列中。draw的完整逻辑见 data.py 中的ConjectureData.draw。收缩shrinker 在choice 序列上进行。Chooser与ChoiceTreechoicetree.py记录收缩过程中做出的选择Chooser.choose(...)返回一个不会导致分支耗尽exhausted的元素ChoiceTree的每个TreeNode记录哪些子选择已经死亡DeadNode从而确保每次收缩尝试都探索新的可能性。验证每次新的 choice 序列都会被重放——用更小的选择重新执行一次完整的生成与测试看是否仍然触发失败。Shrinkercommon.py维护当前值、谓词与已访问集合__seen目标是更小更简单。因为收缩作用于选择序列而非最终值所以生成与收缩天然不可能脱节任何能通过该策略生成的值其收缩结果也必然来自同一策略所有生成时成立的约束例如x * 2必为偶数在收缩后依然成立。测试佐证收缩后的值仍满足策略约束仓库中的测试直接印证了这一点。以 test_slices.py 中的test_slices_will_shrink为例def test_slices_will_shrink(size): sliced minimal(st.slices(size)) assert sliced.start 0 or sliced.start is None assert sliced.stop 0 or sliced.stop is None assert sliced.step is None它使用测试工具minimaltests/common/debug.py内部通过givenPhases.generate/Phase.shrink找到满足条件的最小值验证st.slices(size)收缩到最小之后所有切片字段都落在策略允许的范围内——start/stop收缩为 0 或Nonestep收缩为None。收缩结果仍然遵守生成器的约束这正是收缩输入设计正确性的直接体现。另外test_map.py 中的test_identity_map_is_noop验证了恒等映射被优化为原策略s.map(lambda x: x) is s说明map被设计为纯粹的、不引入任何额外收缩行为的组合子。两种路径的比较从零实现属性测试库时选哪条作者明确给出结论Hypothesis 的实现更优。基于统一 IR策略组合map/filter/flatmap/one_of/composite……都自动保留收缩组合自由度最高。test.check 的实现也完全体面并且对从零实现一个属性测试系统的人而言更容易照搬玫瑰树的概念简单直观map就是树的函子映射fmap。无论从哪条路径起步都要带走同一个关键思想可以用收缩输入来实现收缩输出策略的组合方式必须保留收缩能力。组合性对照表方案收缩对象map 组合是否保留收缩实现复杂度类型驱动收缩Haskell QuickCheck 早期思路值 类型否缩到类型合法但不属于策略约束的值低但正确性差值驱动收缩theft、QuickTheories值返回惰性收缩列表否需要对映射函数求逆一般不可能中玫瑰树收缩clojure.test.check整棵惰性值树是对树整体施加映射中高统一 IR 收缩Hypothesis选择序列与具体值无关是映射只是函数套函数高但组合性最好用户体验层面的收益这套设计的实际价值在于用户几乎或完全不需要自己编写收缩函数。在类型驱动或值驱动的世界里复杂数据往往需要用户配套提供 shrinker否则收缩质量很差而在 Hypothesis 中只要能生成就能收缩。收缩与生成出错的潜在位置大幅减少。生成器与收缩器不再需要同步维护——因为它们是同一份代码收缩只是用更小的选择序列重新执行生成。这从根源上消灭了一整类生成器改了、收缩器忘了改的 bug。总结从 website/content/2016-12-08-compositional-shrinking.md 的论述出发结合当前仓库源码可以确认收缩不应基于值的类型也不应基于值本身而应基于产生这些值的生成过程Hypothesis 即选择序列 IR收缩输出 ≈ 收缩输入是让map、flatmap、filter、one_of等一切策略组合自动保留收缩能力的钥匙从源码看Hypothesis 的 SearchStrategy.map 只是包了一层 MappedStrategy后者在do_draw中画底层值 应用 pack收缩器则在 choice 序列层面 工作——两者解耦又天然一致。如果你正在实现一个属性测试库请从玫瑰树或统一 IR 起步如果你正在使用属性测试库请确认它属于策略组合保留收缩阵营——这决定了失败用例的缩减结果是否真的有意义。赞分享测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载相关推荐Hypothesis项目中的策略收缩设计指南Hypothesis项目中的策略收缩设计指南 引言为什么策略收缩如此重要 在基于属性的测试Property Based Testing中Hypothe测试开发工具如何利用Hypothesis进行高效属性测试从策略到收缩器的完整指南如何利用Hypothesis进行高效属性测试从策略到收缩器的完整指南 Hypothesis是一个功能强大、灵活且易于使用的属性测试库它通过自动生成测试用例来测试开发工具PySCF中使用非收缩基组进行CCSD/CCSD(T)密度拟合计算PySCF中使用非收缩基组进行CCSD/CCSD T 密度拟合计算 在量子化学计算中耦合簇 CCSD/CCSD T 方法是获得高精度电子相关能的重要工具。Py科学计算科研高性能计算上一篇【亲测免费】 数据连接器SDK及示例Power Query与Power BI的完美伴侣下一篇如何安全移除npm全局sudo权限npm-g_nosudo工具10分钟快速上手教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

朴素贝叶斯实现情感文本分类:源码解析与实战避坑指南

朴素贝叶斯实现情感文本分类:源码解析与实战避坑指南

简介:面向计算机相关专业学生与从业者,基于朴素贝叶斯算法的情感文本分析与分类项目源码及数据集,是期末大作业的完整方案。项目以微博短文本情感分类为核心,利用预训练词向量完成文本向量化,配合朴素贝叶斯分类器实现…

2026/9/25 6:10:51 阅读更多 →
ShardingSphere分库分表实战:千万级订单系统的多数据源协同方案

ShardingSphere分库分表实战:千万级订单系统的多数据源协同方案

简介:这是一份面向Spring Boot中高级开发者的技术实践项目,聚焦多数据源管理与数据库分库分表核心场景,解决高并发下单库性能瓶颈与读写分离需求。资源基于Spring Boot 2.x构建,集成MyBatis-Plus简化DAO层开发,采用dyn…

2026/9/25 6:10:51 阅读更多 →
US-Cities-Database地理数据实战指南:加载、清洗与GIS应用

US-Cities-Database地理数据实战指南:加载、清洗与GIS应用

简介:本资源是一个结构完整、开箱即用的美国城市地理信息数据库,面向GIS开发、数据分析、Web地图应用及地理教学等场景的初中级开发者与研究者。数据覆盖全美城市名称、所属州、邮政编码、经纬度等核心字段,支持地理可视化、区域统计与空间查…

2026/9/25 6:10:51 阅读更多 →

最新新闻

ClaudeCode 四层架构拆解:用 TaoToken 统一 Key 打通配置链路

ClaudeCode 四层架构拆解:用 TaoToken 统一 Key 打通配置链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 10:21:10 阅读更多 →
2026企业智能体平台选型指南:OpenClaw替代方案与TaoToken统一接入配置实战

2026企业智能体平台选型指南:OpenClaw替代方案与TaoToken统一接入配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 10:21:10 阅读更多 →
林州省心的GEO推广服务团队怎么选,避坑挑选指南

林州省心的GEO推广服务团队怎么选,避坑挑选指南

林州省心的GEO推广服务团队怎么选?避坑挑选指南来了GEO(生成式引擎优化)是针对AI搜索引擎和智能问答平台的优化服务,核心价值是帮助林州本地企业工厂在AI搜索推荐中抢占靠前位置,让目标客户通过AI工具找到您,获得精准获客线索。我是河南千度…

2026/9/25 10:21:10 阅读更多 →
2026年发电机租赁服务商选购参考汇总:临时用电与长期采购需求精准匹配

2026年发电机租赁服务商选购参考汇总:临时用电与长期采购需求精准匹配

发电机租赁服务商怎么选才能兼顾临时用电与长期采购的需求?为什么很多客户做完发电机租赁后,再做设备采购或置换还会踩坑?挑选同时满足租赁、出售、配套服务的服务商,有哪些可落地的参考标准?很多有临时用电需求的工地、户外项目团队,选发…

2026/9/25 10:21:10 阅读更多 →
KytyPS5 Vulkan渲染管线全解:从PM4图形命令流到像素输出的完整GPU模拟链路

KytyPS5 Vulkan渲染管线全解:从PM4图形命令流到像素输出的完整GPU模拟链路

KytyPS5 Vulkan渲染管线全解:从PM4图形命令流到像素输出的完整GPU模拟链路 【免费下载链接】KytyPS5 PlayStation 5 emulator for Windows, Linux and MacOS 项目地址: https://gitcode.com/gh_mirrors/ky/KytyPS5 KytyPS5 是一款支持 Windows、Linux 与 mac…

2026/9/25 10:21:10 阅读更多 →
2026年高定整木全屋定制工厂招代理加盟选哪家好,发展现状与市场占有率及排名研究分析报告

2026年高定整木全屋定制工厂招代理加盟选哪家好,发展现状与市场占有率及排名研究分析报告

当下国内家居消费市场正迎来结构性升级,别墅、大平层与私宅装修需求持续攀升,高定整木全屋定制赛道的热度不断走高,越来越多创业者选择入局高定木作代理加盟,寻找兼具实力与发展潜力的合作工厂。在一众品牌中,找到扎根…

2026/9/25 10:20:10 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →