首页/新闻资讯/正文详情

用 Hypothesis 将测试写成完整规格:以二分查找的属性测试实战为例

发布时间:2026/9/25 6:09:43 来源:云帆数科 栏目:资讯中心
用 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),仅供参考

相关推荐

RedwoodJS 部署到 Netlify 实战指南:从零到生产环境的 Serverless 全栈上线
RedwoodJS 部署到 Netlify 实战指南:从零到生产环境的 Serverless 全栈上线

后端前端Web框架开发工具 【免费下载链接】redwood RedwoodGraphQL 项目地址: https://gitcode.com/gh_mirrors/re/redwood 点击查看 免费下载 Netlify 是 RedwoodJS 官方推荐的一键式 Serverless 部署平台,也是体验“纯静态托管 云函数 API”全栈 Jam… · 2026/9/25 6:09:25

Humanizer FluentDate 之 In.Two:用「2」做自然流畅的日期偏移计算
Humanizer FluentDate 之 In.Two:用「2」做自然流畅的日期偏移计算

开发工具 【免费下载链接】Humanizer Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities 项目地址: https://gitcode.com/gh_mirrors/hu/Humanizer 点击查看 免费下载 本篇技… · 2026/9/25 6:09:25

RVC-WebUI训练完全指南:如何用10分钟音频克隆出高相似度AI声线
RVC-WebUI训练完全指南:如何用10分钟音频克隆出高相似度AI声线

RVC-WebUI训练完全指南:如何用10分钟音频克隆出高相似度AI声线 【免费下载链接】rvc-webui liujing04/Retrieval-based-Voice-Conversion-WebUI reconstruction project 项目地址: https://gitcode.com/gh_mirrors/rv/rvc-webui RVC-WebUI(Retrie… · 2026/9/25 6:09:19

BAML Rust CLI:从安装、本地构建到 baml-cli 命令行入口的源码剖析
BAML Rust CLI:从安装、本地构建到 baml-cli 命令行入口的源码剖析

编程语言AI Agent编译器CLI人工智能 【免费下载链接】baml The programming language for agents 项目地址: https://gitcode.com/gh_mirrors/ba/baml 点击查看 免费下载 本文以 languages/rust/baml-cli/README.md 为蓝本,完整覆盖 BAML v0 Rust CLI 的… · 2026/9/25 6:51:30

AI Short 离线部署实战:企业内网无后端纯静态提示词库的搭建、迁移与运维
AI Short 离线部署实战:企业内网无后端纯静态提示词库的搭建、迁移与运维

AI 应用提示工程人工智能前端 【免费下载链接】ChatGPT-Shortcut Stop writing prompts from scratch — a searchable prompt library for ChatGPT, Claude, Gemini and Cursor Русский 한국어 العربية हिन्दी ไทย | 别再从头写提示词&… · 2026/9/25 6:51:30

网页视频嗅探:把猫抓扩展用起来的完整流程
网页视频嗅探:把猫抓扩展用起来的完整流程

网页视频嗅探:把猫抓扩展用起来的完整流程 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你正在看一节网课视频,页面只给在… · 2026/9/25 6:51:30

SSM框架实战:大学生兼职系统从设计到部署全解析
SSM框架实战:大学生兼职系统从设计到部署全解析

前阵子帮一个学弟把他的毕业设计项目整体过了一遍代码和逻辑,项目名字就叫“SSM222的大学生兼职系统”。乍一看这个名字很普通,甚至有点像随手起的编号,但把代码跑起来、把业务流程完整走通之后,我发现这个项目作为SSM框架的练手案… · 2026/9/25 6:51:24

SECS/GEM高速源码方案:HSMS握手到状态机落地的避坑指南
SECS/GEM高速源码方案:HSMS握手到状态机落地的避坑指南

/* 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:51:18

PaddleSpeech SpeedySpeech 链路测试脚本详解:从 lite 快速训练到 Paddle Inference 推理验证
PaddleSpeech SpeedySpeech 链路测试脚本详解:从 lite 快速训练到 Paddle Inference 推理验证

人工智能语音音频 【免费下载链接】PaddleSpeech Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword… · 2026/9/25 6:51:06

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* 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 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* 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 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* 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 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码