使用st.deferred()与Hypothesis生成指定键值策略字典未达预期求助
问题:Hypothesis生成字典时键值策略映射错误
需要生成类复合类结构的测试字典,要求name键对应字符串值,fanout_ref键对应布尔值列表。使用Hypothesis的st.deferred结合lambda实现时,出现键值策略不匹配的问题:值可能是键名本身,或者错误的策略类型(比如name对应布尔列表)。
错误代码
name_strategy = st.text(min_size=0, max_size=10) fanout_ref_strategy = st.lists(st.booleans()) MyClass_keys = [ "name", "fanout_ref", ] MyClass_strategy_mapping = { "name": name_strategy, "fanout_ref": fanout_ref_strategy, } MyClass_value_strategy = st.deferred(lambda: MyClass_key_strategy.map(MyClass_strategy_mapping.get) | MyClass_key_strategy) MyClass_key_strategy = st.sampled_from(MyClass_keys) MyClass_strategy = st.dictionaries(keys=MyClass_key_strategy, values=MyClass_value_strategy) @given(specific_dict=MyClass_strategy) def test_specific_dictionary(specific_dict): # Your test code here print(specific_dict)
错误输出
{} {} {'name': text(max_size=10)} {'fanout_ref': 'name', 'name': text(max_size=10)} {'fanout_ref': text(max_size=10), 'name': text(max_size=10)} {'fanout_ref': lists(booleans())} {'fanout_ref': lists(booleans()), 'name': lists(booleans())} {'fanout_ref': text(max_size=10), 'name': lists(booleans())} {'name': lists(booleans()), 'fanout_ref': lists(booleans())} {'name': lists(booleans()), 'fanout_ref': 'fanout_ref'} {'name': text(max_size=10)} {'name': text(max_size=10)}
问题原因
MyClass_value_strategy中的map(MyClass_strategy_mapping.get)返回的是策略对象(比如text(max_size=10)),而不是策略生成的实际值,这是对map方法的误用。| MyClass_key_strategy分支会直接将键名作为值生成,完全不符合需求。st.dictionaries的values参数如果是固定策略,无法根据不同的键动态选择对应的生成逻辑。
解决方法
使用Hypothesis的value_factory参数(Hypothesis 6.0及以上版本支持),可以为每个生成的键指定对应的取值策略,这是最简洁的解决方案:
import hypothesis.strategies as st name_strategy = st.text(min_size=0, max_size=10) fanout_ref_strategy = st.lists(st.booleans()) MyClass_strategy_mapping = { "name": name_strategy, "fanout_ref": fanout_ref_strategy, } MyClass_strategy = st.dictionaries( keys=st.sampled_from(list(MyClass_strategy_mapping.keys())), # 为每个键匹配对应的生成策略 value_factory=lambda key: MyClass_strategy_mapping[key], # 可选:确保字典至少包含一个键,避免空字典 min_size=1 ) @given(specific_dict=MyClass_strategy) def test_specific_dictionary(specific_dict): print(specific_dict) # 验证值类型符合预期 for key, value in specific_dict.items(): if key == "name": assert isinstance(value, str) elif key == "fanout_ref": assert isinstance(value, list) assert all(isinstance(b, bool) for b in value)
如果需要更灵活的生成逻辑(比如动态决定键的数量或嵌套结构),可以使用st.data()手动控制生成流程:
import hypothesis.strategies as st from hypothesis import given, data name_strategy = st.text(min_size=0, max_size=10) fanout_ref_strategy = st.lists(st.booleans()) MyClass_keys = ["name", "fanout_ref"] MyClass_strategy_mapping = { "name": name_strategy, "fanout_ref": fanout_ref_strategy, } @given(d=data()) def test_specific_dictionary(d): # 随机选择要包含的键(至少一个) selected_keys = d.draw(st.sets(st.sampled_from(MyClass_keys), min_size=1)) specific_dict = {} for key in selected_keys: # 根据键选择对应的策略生成值 specific_dict[key] = d.draw(MyClass_strategy_mapping[key]) print(specific_dict) # 验证值类型 for key, value in specific_dict.items(): if key == "name": assert isinstance(value, str) elif key == "fanout_ref": assert isinstance(value, list) assert all(isinstance(b, bool) for b in value)
效果说明
修改后生成的字典会严格匹配键值策略:
name键的值始终是长度0-10的字符串fanout_ref键的值始终是布尔值组成的列表- 不会再出现空字典、键名作为值、策略对象作为值的错误情况
内容的提问来源于stack exchange,提问作者hadromi
相关产品推荐
相关产品推荐

