Generating list of lists with custom value limitations with Hypothesis

后端 未结 3 1631
日久生厌
日久生厌 2021-02-20 02:03

The Story:

Currently, I have a function-under-test that expects a list of lists of integers with the following rules:

  1. number of s
3条回答
  •  被撕碎了的回忆
    2021-02-20 02:36

    There's a good general technique that is often useful when trying to solve tricky constraints like this: try to build something that looks a bit like what you want but doesn't satisfy all the constraints and then compose it with a function that modifies it (e.g. by throwing away the bad bits or patching up bits that don't quite work) to make it satisfy the constraints.

    For your case, you could do something like the following:

    from hypothesis.strategies import builds, lists, integers
    
    def prune_list(ls):
        n = len(ls)
        return [
           [i for i in sublist if i < n][:5]
           for sublist in ls
        ]
    
    limited_list_strategy = builds(
       prune_list,
       lists(lists(integers(0, 49), average_size=5), max_size=50, min_size=1)
    )
    

    In this we:

    1. Generate a list that looks roughly right (it's a list of list of integers and the integers are in the same range as all possible indices that could be valid).
    2. Prune out any invalid indices from the sublists
    3. Truncate any sublists that still have more than 5 elements in them

    The result should satisfy all three conditions you needed.

    The average_size parameter isn't strictly necessary but in experimenting with this I found it was a bit too prone to producing empty sublists otherwise.

    ETA: Apologies. I've just realised that I misread one of your conditions - this doesn't actually do quite what you want because it doesn't ensure each list is the same length. Here's a way to modify this to fix that (it gets a bit more complicated, so I've switched to using composite instead of builds):

    from hypothesis.strategies import composite, lists, integers, permutations
    
    
    @composite
    def limisted_lists(draw):
        ls = draw(
            lists(lists(integers(0, 49), average_size=5), max_size=50, min_size=1)
        )
        filler = draw(permutations(range(50)))
        sublist_length = draw(integers(0, 5))
    
        n = len(ls)
        pruned = [
           [i for i in sublist if i < n][:sublist_length]
           for sublist in ls
        ]
    
        for sublist in pruned:
            for i in filler:
                if len(sublist) == sublist_length:
                   break
                elif i < n:
                   sublist.append(i)
        return pruned
    

    The idea is that we generate a "filler" list that provides the defaults for what a sublist looks like (so they will tend to shrink in the direction of being more similar to eachother) and then draw the length of the sublists to prune to to get that consistency.

    This has got pretty complicated I admit. You might want to use RecursivelyIronic's flatmap based version. The main reason I prefer this over that is that it will tend to shrink better, so you'll get nicer examples out of it.

提交回复
热议问题