新闻详情

新闻详情

首页 / 资讯中心 / 详情

Hypothesis 复杂输入生成全指南:从 `from_type` 到 `composite` 与 `data` 策略

发布时间:2026/9/25 6:55:18来源:尧图网络
Hypothesis 复杂输入生成全指南:从 `from_type` 到 `composite` 与 `data` 策略
测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载属性测试property-based testing入门之后往往会遇到两个真正的难点什么样的属性值得测试以及如何生成满足前置条件的复杂输入——而后者正是本篇的核心主题。本文以考试Exam与考试实例ExamInstance这一典型业务模型为贯穿案例系统讲解 Hypothesis 中从from_type类型推断、filter/assume过滤、builds构造、composite组合到data交互式生成的全部复杂数据生成手段并结合当前仓库Hypothesis 官方开源实现的源码逐一印证各 API 的底层机制。读完本文你将能根据数据的前置条件与相互依赖关系选出最合适的策略组合写出既高效又可复现的属性测试。问题定义需要生成彼此关联的复杂数据假设业务中存在一个考试领域模型from dataclasses import dataclass dataclass class Exam: The abstract exam. Students take ExamInstances. name: str answer_key: list[int] dataclass class ExamInstance: The instantiation of the exam. student: int exam: Exam answers: list[int | None] # must be same length, but answers may be blank对于属性测试我们既需要生成Exam也需要生成ExamInstance。这里有两类约束Exam自身的前置条件例如answer_key中的每个元素都应落在合理区间如 15而不是任意整数跨对象的依赖约束ExamInstance.answers的长度必须与Exam.answer_key的长度完全一致且允许空答案None。这类数据依赖数据的需求远比生成一个整数复杂也正是 Hypothesis 多种进阶策略存在的意义。下面按由简到繁、由弱到强的顺序逐一展开。第一层from_type类型推断——零成本起步Exam的每个字段都已标注类型因此 Hypothesis 的from_type策略可以直接根据类型注解推断出可用的生成器from hypothesis import given import hypothesis.strategies as st given(st.from_type(Exam)) def test_1(exam): ...甚至连ExamInstance也能被直接推断生成因为它的所有字段同样有完整类型注解given(st.from_type(ExamInstance)) def test_2(ei): ...从源码看from_type的解析遵循一套明确的查找顺序见 strategies/_internal/core.py若类型存在于默认查找表或用户注册表直接返回对应策略默认表覆盖了所有 Hypothesis 内置类型并尽可能包含 numpy、pandas 等 extras若来自typing模块走专门逻辑若该类型在合并后的查找表中有若干子类型返回这些子类型策略的并集若该类型所有必选参数都有类型注解且非抽象类则通过builds解析dataclass 天然命中此分支抽象类型按具体子类的并集处理——注意该查找基于继承而非ABCMeta.register所以后者的注册场景仍可能需要手动register_type_strategy。s在from_type源码中还提供了一个除特定类型以外的一切配方everything_except用于测试非法输入的拒绝逻辑值得收藏。何时from_type不够用对Exam场景而言纯类型推断有两个明显不足状态空间过大answer_key会被当作普通整数列表生成比如出现[-45, 0, 800002]这种无意义数据。搜索空间越窄越容易命中有趣的边界情况产生大量荒谬考试默认生成器不关心业务约束。因此我们需要更严格、更贴近业务语义的定制策略。第二层filter与assume——快速过滤但代价高昂当约束简单、且大部分随机输入都合法时可以用filter所有策略都有的方法在策略层过滤或用assume写在测试函数体内声明这个测试用例无效两者为假时都会让 Hypothesis 丢弃该次数据并重新抽取。assume还能用来建立多个参数之间的关联# very inefficient - dont do this! given(st.from_type(Exam).filter(lambda x: x.answer_key and min(x.answer_key) 0)) def test_2(exam): assume(max(exam.answer_key) 5) ...assume的底层实现在 control.py当条件为假时抛出一个UnsatisfiedAssumption异常把该用例标记为不符合假设而非测试失败并让引擎在后续生成中主动避开相似的输入filter与之类似在抽取阶段即完成淘汰。这种做法的优点是写法直观、约束表达清晰缺点是坏输入仍在生成只是被立刻丢弃导致测试变慢严重时 Hypothesis 可能找不到足够多的合法输入HealthCheck 甚至会告警。更优的做法是直接写一个永远生成好输入的定制生成器。第三层builds——用字段级策略构造对象builds接受一个目标对象和一组初始化策略从各策略中抽取对应值再作为参数传入目标对象的__init__。要让answer_key只含 15 的整数given(st.builds(Exam, answer_keyst.lists(st.integers(1, 5)))) def test_3(exam): for x in exam.answer_key: assert x in range(6)name未指定因此 Hypothesis 使用文本的默认生成器。若希望它恒为同一值可用just策略源码见 strategies/_internal/misc.py它只生成给定值且不参与收缩given(st.builds(Exam, namest.just(), answer_keyst.lists(st.integers(1, 5)))) def test_3(exam): # same把定制逻辑收敛为独立函数是更好的工程实践def exam_strategy(namesst.just(), n_options5): return st.builds( Exam, namenames, answer_keyst.lists(st.integers(1, n_options)), ) given(exam_strategy()) def test_3(exam): # same关于builds源码strategies/_internal/core.py透露了几个实用细节未显式传入的策略会自动根据目标对象的类型注解推断对 attrs 类则尽力参考其标准 validator对带默认值的可选参数可显式传...Ellipsis告诉builds为该参数推断策略否则默认参数不会被生成递归类型在解析时会自动延迟deferred因此register_type_strategy(cls, builds(cls, ...))这类自引用写法可以直接工作收缩shrinking通过收缩传入构造函数的参数值来实现。第四层register_type_strategy——把定制策略焊回类型有了定制生成器后可以让 Hypothesis 在推断Exam时始终使用它def exam_strategy(): ... st.register_type_strategy(Exam, exam_strategy())此后任何需要推断Exam的地方——包括作为ExamInstance的一部分被生成时——都会自动采用该策略given(st.from_type(Exam)) def test_4(exam): ...源码strategies/_internal/core.py说明该注册表同时服务builds与givenstrategy参数既可以是策略实例也可以是一个接收类型、返回策略的函数对泛型类型很有用该函数返回NotImplemented表示本类型不提供策略交还默认解析。需要注意的是不能直接注册参数化泛型如MyCollection[int]而应注册MyCollection并用函数检查其类型参数。register_type_strategy解决了Exam的生成问题但ExamInstance.answers与exam.answer_key的长度相等这一跨对象约束仍然无解——builds无法把两个策略链接起来# This will fail given(st.from_type(ExamInstance)) def test_5(ei): assert len(ei.answers) len(ei.exam.answer_key)要组合多个相互依赖的策略需要更强大的工具composite。第五层composite——把绘制流程提升为完整策略composite是用户编写的函数经装饰器提升为完整策略。先看一个简单例子生成一个列表其中首元素恒为列表最小值。st.composite def example(draw): i draw(st.integers()) l draw(st.lists(st.integers(min_valuei))) return [i] l关键点在于内层函数返回的是普通 Python 值而不是策略composite装饰器把该函数转换为策略并通过隐藏的draw参数调用完整函数时无需显式传入从任意策略中抽取具体值。调用方式与普通策略无异given(example()) def test_example(l): assert min(l) l[0]现在用composite解决答案长度一致的约束。先定义一个辅助策略它根据给定 exam 生成与answer_key等长、可含空答案的答案列表# Helper function to make the following examples terser def ei_answers_strategy(exam): return st.lists( st.none() | st.integers(1, 5), min_sizelen(exam.answer_key), max_sizelen(exam.answer_key), ) st.composite def exam_instance_strategy(draw): exam draw(st.from_type(Exam)) answers ei_answers_strategy(exam) return ExamInstance(student1, examexam, answersdraw(answers))用法与任何策略一致given(exam_instance_strategy()) def test_6(ei): assert len(ei.answers) len(ei.exam.answer_key)组合可复用的composite生成相互关联的多个实例composite函数本质是函数因此可以互相调用构造更复杂的关联数据。假设要为同一个学生、同一场考试生成多份ExamInstance可以拆成两层可组合的部件st.composite def exam_instance_for_exam(draw, exam, student): answers ei_answers_strategy(exam) return ExamInstance(studentstudent, examexam, answersdraw(answers)) st.composite def many_exam_instances(draw, student): exam draw(st.from_type(Exam)) exams st.lists(exam_instance_for_exam(exam, student), min_size1) return draw(exams) given(many_exam_instances(brian)) def test_7(eis): ...注意上面向composite传入的是普通值学生名无法被随机化。收紧状态空间是它的优点但一成不变也违背属性测试精神。若需要更充分的测试可以改传策略并在函数体内drawst.composite def exam_instance_for_exam(draw, exam, studentst.just()): student draw(student) exam draw(exam) answers ei_answers_strategy(exam) return ExamInstance(studentstudent, examexam, answersdraw(answers)) # many_exam_instances is the same... for now不过这里有一个可用性问题当many_exam_instances传入复杂的exam/student策略时调用方无法知道实际抽到了什么值。在本例中可以从返回的ExamInstance反推但更复杂的变换就不一定可靠。因此最佳实践是把所有draw出的值一并返回st.composite def exam_instance_for_exam(draw, exam, studentst.just()): - return ExamInstance(studentstudent, examexam, answersdraw(answers)) return exam, student, ExamInstance(studentstudent, examexam, answersdraw(answers))返回元组后many_exam_instances与测试也要相应调整——lists不能再直接消费exam_instance_for_exam的结果它已不再是单一ExamInstance。可以在lists外层用.map(lambda x: x[2])提取或退而用循环逐一draw后者非惯用写法st.composite def many_exam_instances(draw, studentst.just()): exam draw(st.from_type(Exam)) student draw(student) # Unidiomatic/inefficient: getting the values in a loop number draw(st.integers(1, 5)) instances [] for _ in range(number): ei_strategy exam_instance_for_exam(examst.just(exam), studentst.just(student)) _, _, ei draw(ei_strategy) instances.append(ei) # Best practice: use lists(), in this case after .map() instance_strat exam_instance_for_exam(exam, student).map(lambda x: x[2]) instances draw(st.lists(instance_strat, min_size1)) return number, exam, student, instances given(many_exam_instances(studentst.characters())) def test_8(stuff): _, exam, _, eis stuff ...如你所见把策略传进composite会带来不少样板代码是否值得取决于具体场景。composite的底层行为strategies/_internal/core.py值得注意它的draw参数必须位于签名首位若装饰类方法或类方法draw必须排在self/cls之前因此官方建议优先写成独立函数再配合register_type_strategy关联到类型composite也可与classmethod/staticmethod叠加其示例通过收缩每次 draw 的输出来实现整体收缩。另外composite不能混合测试代码与生成代码——这正是data的用武之地。第六层data——在测试体内交互式生成生成复杂输入的最后一招是data策略。它与composite一样提供draw但可以在测试函数体内使用把生成与测试逻辑混合在一起given(st.data(), st.from_type(Exam)) def test_data(data, exam): answers data.draw(ei_answers_strategy(exam)) ei ExamInstance(student1, examexam, answersanswers) assert len(ei.answers) len(ei.exam.answer_key)data的核心优势是策略可根据测试内行为动态定制if f(x): y data.draw(st.integers(min_value1, max_value10)) else: y data.draw(st.integers(min_value6, max_value20))源码strategies/_internal/core.py进一步补充了几个实用点每次data.draw()都可在失败输出中逐条打印便于定位还支持label参数为每个 draw 命名失败时输出Draw 1 (First number): 0这类可读信息其示例的收缩方式同样是收缩每次 draw 的输出。但data与部分 Hypothesis 特性配合不佳例如example()装饰器且错误报告更复杂。如果数据可以预先全部生成优先使用composite只有确实需要看着测试结果决定下一步生成什么时才用data。总结何时用哪种策略围绕本文的考试模型可以画出一条清晰的能力阶梯场景推荐工具底层依据类型注解齐全、约束宽松st.from_type自动类型解析含builds回退与抽象类型并集大部分输入合法、仅需排除少数边界filter/assumefilter在抽取层淘汰assume抛UnsatisfiedAssumption引导搜索字段级约束明确st.builds 各字段策略抽取参数值后调用目标构造器缺失参数按注解推断可选参数可用...触发推断让推断始终使用定制生成器st.register_type_strategy全局类型→策略查找表供builds与given共用需要多个 draw 彼此关联st.composite函数被提升为策略draw注入可互相调用、可组合lists/.map需要在测试体内按行为动态生成st.data测试内data.draw支持 label 与逐 draw 收缩但与example()等特性不兼容一句话记忆from_type负责白拿assume/filter负责丢builds负责构造composite负责关联data负责交互而register_type_strategy可以把所有定制策略焊回类型系统。Hypothesis 为复杂数据生成提供了大量机制掌握何时用哪种确实是一门手艺但值得投入——它直接决定属性测试的生成效率、边界覆盖质量与可维护性。本文以 2020-06-08-complex-data-strategies.md 为基础骨架并结合仓库源码strategies/_internal/core.py、strategies/_internal/misc.py、control.py展开文中所有 API 行为与参数均以当前仓库实现为准。赞分享测试开发工具【免费下载链接】hypothesisThe property-based testing library for Python项目地址https://gitcode.com/gh_mirrors/hy/hypothesis点击查看免费下载相关推荐ScreenshotFramer深度解析如何批量生成多语言应用商店截图ScreenshotFramer深度解析如何批量生成多语言应用商店截图 想要让你的应用在全球市场脱颖而出吗ScreenshotFramer 是一款强大的批量终极指南深入理解Hypothesis策略系统 - 从整数到复杂数据结构的完整教程终极指南深入理解Hypothesis策略系统 从整数到复杂数据结构的完整教程 Hypothesis是一个强大、灵活且易于使用的属性测试库能够自动生成测试数据测试开发工具如何快速解决Loop窗口管理器的快捷键冲突问题终极完整指南如何快速解决Loop窗口管理器的快捷键冲突问题终极完整指南 你是否在使用Loop窗口管理器时遇到过快捷键无效或执行错误操作的困扰这很可能是 Loop快捷键冲测试开发工具上一篇qryn一款多语言观测性开源项目下一篇AutoTrain Advanced与TikTok集成短视频内容分析与推荐系统搭建指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

AVRDUDESS图形化烧录工具实战指南:从入门到产线应用 2026/9/25 7:29:23

AVRDUDESS图形化烧录工具实战指南:从入门到产线应用

1. 项目概述:为什么一个带图形界面的AVR烧录工具值得你花20分钟认真读完 AVRDUDESS不是什么新概念,它本质上就是AVRDUDE的图形外壳——但这句话背后藏着太多新手踩坑、老手绕路的真实故事。我第一次用AVR单片机是在2013年,当时在Windows下配…

阅读更多 →
CTF MISC入门:文件类型识别、分离与合并实战指南 2026/9/25 7:29:16

CTF MISC入门:文件类型识别、分离与合并实战指南

简介:这份PDF资料面向CTF竞赛初学者及希望提升杂项解题能力的安全爱好者,系统梳理了MISC方向的基础知识点与实战技巧。内容围绕文件类型识别、文件分离与文件合并三大模块展开,涵盖file命令、010Editor、Binwalk、foremost、dd、fcrackzip等工…

阅读更多 →
转行网络安全必看:从认知到落地四个关键步骤 2026/9/25 7:29:10

转行网络安全必看:从认知到落地四个关键步骤

身边想做网络安全的人越来越多,我在后台收到过不少类似的提问:我是做运维的、做开发的、甚至做客服的,能不能转行网络安全?网上那些“三个月拿到20K”的说法靠谱吗?我自己带过一些转行的新人,也以面试官的身…

阅读更多 →
基于OPCode N-Gram与XGBoost的PHP Webshell检测实战 2026/9/25 7:29:10

基于OPCode N-Gram与XGBoost的PHP Webshell检测实战

简介:这份资源面向网络安全与机器学习方向的开发者、安全研究人员及高校学生,聚焦 WebShell 检测这一实战课题,提供一套从特征工程到模型训练的完整实现方案。压缩包共 2000 个文件,约 68.61MB,其中 1838 个 php 文件构…

阅读更多 →
Mopidy on Arch Linux:pacman 安装、AUR 扩展生态与 systemd 服务部署实战 2026/9/25 7:29:03

Mopidy on Arch Linux:pacman 安装、AUR 扩展生态与 systemd 服务部署实战

音视频后端 【免费下载链接】mopidy Mopidy is an extensible music server written in Python 项目地址: https://gitcode.com/gh_mirrors/mo/mopidy 点击查看 免费下载 本篇指南面向 Arch Linux 用户,完整讲解如何从官方 Community 仓库安装 Mopidy 音…

阅读更多 →
Two.js SVG 渲染器(Two.SVGRenderer)深度解析:从场景图到 `<svg />` 的渲染管线与实战指南 2026/9/25 7:29:03

Two.js SVG 渲染器(Two.SVGRenderer)深度解析:从场景图到 `<svg />` 的渲染管线与实战指南

图形学前端 【免费下载链接】two.js A renderer agnostic two-dimensional drawing api for the web 项目地址: https://gitcode.com/gh_mirrors/tw/two.js 点击查看 免费下载 Two.SVGRenderer 是 Two.js 渲染器家族中面向矢量 DOM 的实现,也是 new Two…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉