用 Hypothesis 将测试写成完整规格:以二分查找的属性测试实战为例
测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载导读当一个问题被少数几条简单属性完全规定时它的实现可能依然极其繁琐但它的测试却会变得出乎意料地容易。本文以左偏二分查找left-biased binary search为完整实例讲解如何用 Hypothesis 的given与策略组合把函数规格逐条翻译成可自动执行的属性测试同时揭示属性测试与数学证明之间的边界——低概率 bug 的存在及其发现时机并给出二次搜索这类主动制造冲突场景的增强测试技巧。读完本文你将掌握以属性充当完整规格的测试方法论并理解 Hypothesis 测试数据库ExampleDatabase如何让已发现的失败持续复现直至 bug 被修复。一、什么是完全由属性规定的程序在现实工程中多数函数的正确性定义是模糊的我们知道它应该做什么但难以用简洁的条件把它框死。但还有另一类问题——例如二分查找——其结果被几条简单属性完全规定返回值必须是一个合法的插入位置把值插入该位置后列表仍然有序插入到任何更小的位置列表都不再有序。满足这三条的实现无论内部逻辑多绕都必然是正确的。原文档强调这并不代表它们容易实现很多此类问题实际上极其难写对但意味着它们容易测试。这正是属性测试property-based testing的理想土壤属性测试不关心你打算怎么做只关心结果必须满足什么。Hypothesis 的核心主张正是围绕这一点展开——它把生成输入、验证属性变成了一条流水线。二、用 Hypothesis 把规格写成测试原文档给出了一组直接对应上述三条属性的测试。下面的代码做了少量现代化处理以st.lists(st.integers())的写法给出功能与原版一致from hypothesis import given, strategies as st given(st.lists(st.integers()).map(sorted), st.integers()) def test_binary_search_gives_valid_index(ls, v): i binary_search(ls, v) assert 0 i len(ls) given(st.lists(st.integers()).map(sorted), st.integers()) def test_inserting_at_binary_search_remains_sorted(ls, v): i binary_search(ls, v) ls.insert(i, v) assert sorted(ls) ls given(st.lists(st.integers()).map(sorted), st.integers()) def test_inserting_at_smaller_index_gives_unsorted(ls, v): for i in range(binary_search(ls, v)): ls2 list(ls) ls2.insert(i, v) assert sorted(ls2) ! ls三个测试逐条对应规格的三条属性返回合法索引、在该索引插入后仍有序、在更小索引插入则失序。若三者全部通过binary_search的规格就被完整覆盖了。2.1 从源码看given与策略组合这一组测试的核心设施都有明确的源码依据given是 Hypothesis 测试的主入口。在 core.py 中given的文档字符串明确写道Thegivendecorator turns a function into a Hypothesis test. This is the main entry point to Hypothesis. 它既支持位置参数也支持关键字参数并且从右往左填充参数——这意味着放在最左边的位置参数可以留给self便于在unittest.TestCase子类或实例方法中使用这一行为在该段源码的注释中有直接说明。st.integers()生成整数。见 numbers.pyintegers(min_valueNone, max_valueNone)当上下界为None时不设边界其 docstring 指出例子会向 0 收缩负数还会向正数收缩。这正是 Hypothesis 失败用例会自动变小、便于阅读的根本原因。st.lists()生成列表。见 core.pylists(elements, *, min_size0, max_sizeNone, unique_byNone, uniqueFalse)长度落在[min_size, max_size]且通过尝试移除元素来收缩。结合.map(sorted)收缩得到的仍然是有序列表——这与 Hypothesis 将收缩集成进生成的设计一脉相承可对照 integrated-shrinking 一文 中收缩必须满足与生成相同的约束的论述。因此st.lists(st.integers()).map(sorted)的含义是先随机生成任意长度、任意整数的列表再映射为有序列表作为测试输入一旦断言失败Hypothesis 会尝试把这个有序列表收缩到最小反例。三、测试与数学证明的边界低概率 bug 的存在如果这些测试通过我们的实现一定完全正确对吧原文档给出的回答是大多数情况下是但存在一个反复出现的隐患——属性测试无法保证属性在所有输入上都成立。证明给出的是全称保证这些属性总是成立而测试只能保证在被检查的有限样本上成立。Hypothesis 的检查范围远超手写测试但终究是有限集合。差异带来的直接后果就是低概率 bug某个错误行为只有在相当特殊的输入上才会被触发随机生成往往需要多次运行才能撞上。原文档随后给出了一个恰好踩中该陷阱的实现def binary_search(list, value): if not list: return 0 if value list[-1]: return len(list) if value list[0]: return 0 lo 0 hi len(list) - 1 while lo 1 hi: mid (lo hi) // 2 pivot list[mid] if value pivot: hi mid elif value pivot: return mid else: lo mid return hi这个实现犯了一个经典的错误当mid处的元素恰好等于目标值时直接返回mid。这违反了永远返回最小插入位置的第三条属性——[0, 1, 1, 1, 1]中插入1时正确结果应为1而该实现可能在mid 2或更晚时提前返回。3.1 为什么这个 bug 难以被随机撞上原文档给出的失败用例是Failing test case: test_inserting_at_smaller_index_gives_unsorted( ls[0, 1, 1, 1, 1], v1 )有时也会得到ls[-1, 0, 0, 0, 0], v0。触发条件相当苛刻value必须在ls中至少出现两次并且二分过程中某个非首个出现位置恰好被选为mid。Hypothesis 的生成器会刻意提高这种用例出现的概率但提升幅度有限——作者实测通常需要运行 2 到 5 次才会失败一次。这一案例很好地说明了属性测试方法论的一个核心权衡规格完备 ≠ 反例易得。三条属性虽然共同构成了完整规格但第三条属性对重复元素这类特定输入特别敏感而重复元素在完全随机的列表中并不常见。四、失败之后Hypothesis 测试数据库的自动接力原文档接着指出一旦测试开始失败Hypothesis 的测试数据库就会接管让该测试持续失败直到 bug 被修复。这在源码中有完整的对应实现。见 database.pyExampleDatabase的类文档写道——Hypothesis 会自动把失败保存到settings.database指向的数据库中下次运行同一测试时会在reuse阶段重放这些失败对应Phase.reuse。数据库最好被理解为永远不需要失效的缓存。具体机制包括save(key, value)把失败的例子保存到数据库database.pyfetch(key)按测试标识读取已保存的例子database.py运行阶段由Phase枚举驱动explicit显式example、reuse重放数据库中的失败、generate生成新例子等见 _settings.py。于是实际开发体验是低概率 bug 一旦被首次捕获后续每次运行都会先重放该反例持续红灯直到修复。但正如原文档强调的这并不能消除低概率失败的全部成本——它把发现问题的时间点从引入 bug 之时推迟到了某次偶然撞上之时而推迟越久定位成本越高。这在状态化测试中尤为突出由于搜索空间巨大存在大量低概率 bug见 rule-based-stateful-testing 一文对应源码中的RuleBasedStateMachine见 stateful.py。五、修复策略主动制造冲突场景而非被动等待幸运的是这类问题有一个简洁的修复路径编写对示例不敏感的增强测试——不依赖 Hypothesis 恰好生成重复元素而是由测试自身主动制造出容易出问题的结构。原文档给出的方案是搜索—插入—再搜索given(st.lists(st.integers()).map(sorted), st.integers()) def test_inserting_at_result_point_and_searching_again(ls, v): i binary_search(ls, v) ls.insert(i, v) assert binary_search(ls, v) i其正确性论证非常优雅先搜索、在结果位置插入、再搜索一次插入点不可能移动——因为在该位置再插一次结果依然有序而在任何更早位置插入依然失序所以第二次搜索必然返回同一个位置。这个测试几乎稳定失败于那个错误实现因为它不再依赖随机数据里碰巧存在重复而是主动创造了重复把v插回ls恰好制造出一个紧邻的重复对而这样的重复对极可能落在二分查找的探查路径上。这正是原文档提出的核心技巧——用输出引导输入的构造让测试主动把数据推向最可能暴露错误的形态。5.1 与测试优化器方法论的呼应原文档特别提醒读者这个思路与作者此前在 testing-optimizers-with-hypothesis 一文 中使用的技巧同源那里不是直接断言最优解因为无对照而是测试对扰动的正确响应——移除一个已选物品不应改善得分、添加一个已选物品的副本不应降低得分。文章中还展示了用st.data()在测试运行时交互式取数data.draw(st.sampled_from(original_solution))让后续输入依赖函数输出从而构造出最能检验性质的数据。两条方法论的共同点是当完全规格难以直接验证时就验证规格在结构化变更下的不变量。对于二分查找插入后再次搜索位置不变就是这样一个对变更的响应断言对于背包问题增删物品后得分不反向变化也是。这种思路可以推广到更广阔的领域——例如修改用户权限或系统设置后断言其可用选项集合单调不减。六、结论规格测试是起点而非终点原文档以三条结论收尾这里结合全文展开完全规定的程序是属性测试的天然富矿。规格即测试每条属性都可以用 Hypothesis 的given与策略组合直接翻译成自动化断言几乎无需手工构造用例。这是测试的起点而不是终点。属性测试与数学证明之间存在本质差异——前者只能保证有限样本上的正确性。低概率 bug 会真实存在需要开发者继续思考还有哪些有趣的方式去测试软件例如主动制造重复、利用输出构造扰动输入、做状态化测试等。善用 Hypothesis 的失败接力机制。一旦反例出现ExampleDatabase会在reuse阶段自动重放让 bug 持续暴露直至修复而为了尽早发现问题则应主动编写对示例不敏感的增强测试把发现失败的时机从偶然拉回必然。延伸阅读rule-based-stateful-testing状态化测试中的低概率 bug 问题testing-optimizers-with-hypothesis以对扰动的正确响应测试优化器的完整实例integrated-shrinkingHypothesis 将收缩集成进生成的设计理念核心源码given 定义、integers 策略、lists 策略、ExampleDatabase、Phase 枚举。赞分享测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载相关推荐用 Hypothesis 守护 Encode/Decode 不变量以 Run Length Encoding 为例的属性测试实战用 Hypothesis 守护 Encode/Decode 不变量以 Run Length Encoding 为例的属性测试实战 导读不变量invaria测试开发工具Hypothesis 属性测试入门从「写例子」到「描述性质」的测试革命Hypothesis 属性测试入门从「写例子」到「描述性质」的测试革命 Hypothesis 是 Python 生态中最具代表性的属性测试property测试开发工具用 Hypothesis 属性化测试覆盖配置参数以 Argon2 密码哈希库为例用 Hypothesis 属性化测试覆盖配置参数以 Argon2 密码哈希库为例 配置参数测试是软件测试中极易被忽视、却又极易出错的一环。本文基于 Hypot测试开发工具上一篇Tutti安全架构分析如何在多代理环境中保护用户数据和隐私下一篇AI Job Search命令大全掌握所有实用功能创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Learn-Algorithms 海量数据处理实战:分布处理之 MapReduce 原理与 Hadoop 生态

Learn-Algorithms 海量数据处理实战:分布处理之 MapReduce 原理与 Hadoop 生态

教程 【免费下载链接】Learn-Algorithms 算法学习笔记 项目地址: https://gitcode.com/gh_mirrors/le/Learn-Algorithms 点击查看 免费下载 MapReduce 是 Google 提出的并行计算软件架构,专门面向超过 1TB 量级的大规模数据集;它的核心思想—…

2026/9/25 6:09:51 阅读更多 →
OpenCore Legacy Patcher 完整教程:如何给 2007-2017 老 Mac 安装 Sonoma 与 Sequoia

OpenCore Legacy Patcher 完整教程:如何给 2007-2017 老 Mac 安装 Sonoma 与 Sequoia

OpenCore Legacy Patcher 完整教程:如何给 2007-2017 老 Mac 安装 Sonoma 与 Sequoia 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 上周清理旧笔…

2026/9/25 6:09:51 阅读更多 →
WinMerge 单元测试入门指南:用 Google Test 搭建、编译与运行 C++ 测试套件

WinMerge 单元测试入门指南:用 Google Test 搭建、编译与运行 C++ 测试套件

桌面应用开发工具 【免费下载链接】winmerge WinMerge is an Open Source differencing and merging tool for Windows. WinMerge can compare both folders and files, presenting differences in a visual text format that is easy to understand and handle. 项目地址&…

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

最新新闻

从2024年APT报告提炼威胁情报基线:组织画像、检测规则与行业防御实践

从2024年APT报告提炼威胁情报基线:组织画像、检测规则与行业防御实践

简介:《2024年全球高级持续性威胁(APT)研究报告》由360高级威胁研究院发布,基于360安全大模型与全网安全大数据视野,系统梳理2024年全球APT攻击态势、活跃组织与攻击手法,为政企机构、安全运营人员和威胁情…

2026/9/25 6:45:16 阅读更多 →
C#实现企业微信主动消息推送:鉴权、重试与队列全链路

C#实现企业微信主动消息推送:鉴权、重试与队列全链路

/* 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 6:45:16 阅读更多 →
IDA 5.0反汇编工具:32位PE样本静态分析与IDC脚本应用

IDA 5.0反汇编工具:32位PE样本静态分析与IDC脚本应用

/* 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 6:45:16 阅读更多 →
STM32F4 USB CDC大数据传输优化:双缓冲与FIFO分配实战

STM32F4 USB CDC大数据传输优化:双缓冲与FIFO分配实战

/* 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 6:45:16 阅读更多 →
VB6老项目迁移SQLite:litex_sqlite封装库实战指南

VB6老项目迁移SQLite:litex_sqlite封装库实战指南

/* 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 6:45:16 阅读更多 →
Word表格自动上浮与跨页断行问题的根源与解决

Word表格自动上浮与跨页断行问题的根源与解决

/* 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 6:44:15 阅读更多 →

日新闻

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 阅读更多 →