I'm trying to generate complex tree like dictionaries for testing my code. the dictionaries are basically compound classes, that contains list of structs and more simpler fields.

I'm using hypothesis strategies deferred method, with a lambda function implementation, I want to map a certain generated 'key' to a specific strategy made for that 'key'. for some reason it's looks like my mapping doesn't work as expected and different 'keys' are getting different strategies.

would appreciate your assistance. thanks.

This is what I've tried:


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)

This is the generated output I see:

{}
{}
{'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)}

I Was expecting to have dictionaries, with 'name' and 'fanout_ref' as keys, and strings and lists of boolean as values.

1

There are 1 answers

0
Zac Hatfield-Dodds On

The idiomatic way to do this uses st.fixed_dictionaries():

MyClass_strategy = st.fixed_dictionaries(
    {"name": st.text(min_size=0, max_size=10),
     "fanout_ref": st.lists(st.booleans())}
)

Separately, you might want to understand why your example code doesn't work.

One bug is that the keys= and values= strategies for st.dictionaries() are completely independent - if you need specific values per key, you'll need to use fixed_dictionaries(), or generate and merge a dictionary for each group of keys that share a values strategy.

Another bug is that in st.deferred(lambda: MyClass_key_strategy.map(MyClass_strategy_mapping.get) | MyClass_key_strategy)

  • the st.deferred() isn't doing anything
  • the | MyClass_key_strategy says that you can generate values from the keys strategy
  • the .map() returns the strategy as a value; to also draw from the returned strategy you need .flatmap(). Here's the docs; it'd look like = MyClass_key_strategy.flatmap(MyClass_strategy_mapping.get).

Finally, st.dictionaries() doesn't always generate all possible keys - you'd need to set min_size=2 for this, although it's a really bad way to generate from a fixed set of keys relative to fixed_dictionaries.