How to write fuzz tests for a function which generates a dictionary?

Let’s take a step back. Why do you want to fuzz test this function? Unit testing is one of those things that you can find a lot of opinions about on the internet. But as long as you are satisfied that your code is correct, then you are testing well. So maybe start with, what’s the bug surface, what bugs do you want to show are not present in your code?

I’m worried about many repeated words. Perhaps something like this: (I have not compiled these tests; some small tweaks may be necessary)

fuzz (Fuzz.intRange 0 1000) "counts repeated words" <| \i ->
  List.repeat i "barnacles" |> Main.wordsDict |> Expect.equal [Dict.fromList [("barnacles", i)]]

This should give you confidence that you can count large numbers of repetitions, BUT only when there are no other words present and and the word is "barnacles". You could also map over List.range 0 10 and make unit tests for those cases, instead of the fuzz test.

I’m worried about certain strings being treated specially. I’m usually not, if I can look at the code and see that it’s not looking at lengths, prefixes, a word list, etc., and doing nefarious things in those cases. You could write a fuzz test that takes in random strings, or a randoms string to use instead of "barnacles".

But if I’m making a data structure that treats strings generically then… you can use the type system. Why not make the signature wordsDict : List comparable -> List (Dict comparable Int)? By making the function more generic, the compiler will reject any attempts to say, not count the empty string. (Unless that’s desired behavior, in which case, write a unit test.)

I’m worried about counting up all the words. One of the great things about fuzz tests is that you don’t have to compute the output value of the function under test, only some invariant that will be true about it. Let’s test that the length of the input list will equal the sum of the values of the dictionary.

-- use a small word list to ensure duplicates
myWords = ["barnacles", "turtles", "whales", "sharks"]

-- effective Elm programming involves combining simple functions together
listFrom : List a -> Fuzz.Fuzzer (List a)
listFrom words = words |> List.map Fuzz.constant |> Fuzz.oneOf |> Fuzz.list

fuzz (listFrom myWords) "length of input = sum of values of output" <| \input ->
  input |> Main.wordsDict |> Dict.values |> List.sum |> Expect.equal (List.length input)

You can write a similar test to check that all values are positive.

1 Like