Every week I meet for Code and Coffee with other devs to chat about all kind of topics (often related to software) and lately we have been doing Katas from CodeWars under the WpgDotNet clan.
This time around I was working with @QuinnWilson and @AdamKrieger doing the Highest and lowest Kata using Ruby and RSpec.
Is a simple Kata but I wanted to put focus on TDD, self data generation and property testing...
The scenarios
I won't go into the TDD steps because I want to fast forward to data generation.
We created three tests, from the domain of the problem first we selected strings that contain only one number then the result should be that number as max and min.
1 | context 'When the string has only one number' do |
For our second scenario we thought that it would be easy if the sequence of numbers where already sorted, so the min and max are the first
and last
.
1 | context 'When the numbers are sorted' do |
The last scenario includes all the strings with sequence of integers.
1 | context 'For any string with one or more numbers separated by space' do |
Ruby has a minmax
method on Array
but we wanted to do it using a fold
. So here is our implementation:
1 | def highest_lowest(str) |
Self generating data
We did write only one example on each scenario to start but now we wanted to cover more cases.
Not only I want to generate random data but I want to have a reasonable distribution of values and describe it as a property that the function has to satisfy.
Libraries like QuickCheck (for Haskell, main inspiration to others), FsCheck (for F#) and ScalaCheck (for Scala) do exactly that. For Ruby we used Rantly.
Single integer
The first scenario requires a string with one integer.
To do that with Rantly we can use the integer generator integer
. After converting it to a string
we have the input for the test.
Imagine a property that looks like:
Given a string with a single integer N
The result is a string like "{N} {N}"
1 | it 'Returns the same number twice' do |
Rantly is going to generate 100 cases by default and check to make sure the equality holds for all of them.
Sorted integers
Having a sorted sequence of numbers is easy to specify where the min and max should be.
So the property could be something like (in pseudo formal but not so much english):
Given an ordered string of integers
Then the first is the MIN and the last is the MAX
And the result is a string like "{MAX} {MIN}"
1 | context 'When the numbers are sorted' do |
The general case
Thinking of the actual postcondition of the problem, the property could be something like:
Given any non empty collection of integers
And exist MIN and MAX that belong to the collection
Where MAX is the maximum and MIN is the minimum
Then the result is a string like "{MAX} {MIN}"
Considering for a moment the domain (all the possible instances) of any non empty collection of integers. That would include sets of a single element and also sorted elements, thus we cover the other two scenarios.
I want to generate all the integers but at the same time have the max and min so I don't write the test duplicating the implementation to find maximum and minimum.
To do that, I will generate first the min and max and then add integers in between.
1 | it 'returns the max and the min' do |
I changed the number of cases to 200 just to illustrate how to override the default.
Using properties
Properties can be very useful to help identify test cases plus data generation makes much easier to describe a scenario and find a domain for the test.
Having a combination of both property testing and example testing can be a good balance to have tests that are accurate and also descriptive.