POST POST

OCT
12
2015

Self generating data

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
2
3
4
5
6
context 'When the string has only one number' do
it 'Returns the same number twice' do
str = "1"
expect(highest_lowest str).to eq "1 1"
end
end

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
2
3
4
5
6
context 'When the numbers are sorted' do
it 'returns the last and the first' do
str = '-5 -2 8 12 32 150'
expect(highest_lowest str).to eq "150 -5"
end
end

The last scenario includes all the strings with sequence of integers.

1
2
3
4
5
6
context 'For any string with one or more numbers separated by space' do
it 'returns the max and the min' do
str = '22 -5 28 -294 33 1794 10000 2'
expect(highest_lowest str).to eq "10000 -294"
end
end

Ruby has a minmax method on Array but we wanted to do it using a fold. So here is our implementation:

1
2
3
4
5
def highest_lowest(str)
str.split.map(&:to_i).inject([MIN, MAX]) do |mm, i|
[[mm.first, i].max, [mm.last, i].min]
end.join ' '
end

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
2
3
4
5
6
7
8
it 'Returns the same number twice' do
property_of {
n = integer
n.to_s
}.check do |str, n|
expect(highest_lowest str).to eq "#{n} #{n}"
end
end

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
2
3
4
5
6
7
8
9
10
context 'When the numbers are sorted' do
it 'returns the last and the first' do
property_of {
arr = array { integer }.sort
[arr.join(' '), arr.last, arr.first]
}.check { |str, max, min|
expect(highest_lowest str).to eq "#{max} #{min}"
}
end
end

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
2
3
4
5
6
7
8
9
it 'returns the max and the min' do
property_of {
min, max = [integer, integer].minmax
arr = array { range min, max } + [max, min]
[arr.shuffle.join(' '), max, min]
}.check(200) do |str, max, min|
expect(highest_lowest str).to eq "#{max} #{min}"
end
end

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.


Amir Barylko

Email Email
Web Web
Twitter Twitter
GitHub GitHub
LinkedIN LinkedIn
RSS

Looking for someone else?

You can find the rest of the Western Devs Crew here.

© 2015 Western Devs. All Rights Reserved. Design by Karen Chudobiak, Graphic Designer