Browsing Roy Osherove’s blog I saw a short kata tasks designed to test in practice TDD approach to software development. The purpose of this kata was to create a simple calculator using TDD. I thought it would be a nice idea to create and describe the solutions of this Kata by FSCheck, xUnit and C#/F#. Why I think it might be fun? As a rule, such solutions can be found in C# with usage of NUnit or xUnit and here we will try an entirely different approach to writing unit test. at any rate unit.
The aim of this Kata will be to create a “calculator”. Let’s start with the steps that should be implemented:
- Create a class Calculator, which will include the Add method. The method should accept a single parameter of type string.
- We start from the fact that passing an empty string to a method should return 0.
- For single argument should return numeric value of this exact parameter, otherwise it should return 0.
- Function (add) should be converted in such a way that it can accept multiple arguments that the input string will be separated by a space.
- Our method should also be able to separate the input arguments with the newline character ‘\ n’.
- We need to modify our method so that it will accept another argument. This argument will tell our function how the input delimiter looks like. Delimiter should be of type string.
- The next step is to modify the method so that it threw an exception when input argument will contain any negative numbers.
- The last task is that, the numbers greater than 1000 should be skipped during summation.
If you want to do this kata on Your own, here is an empty project with a list of tests to implement.
Here is my resolution :) Well, since we have written in paragraphs what we should do, let’s start with the realization of the first point. To do this, we start by writing a test that will check whether the method add for an empty input string returns 0. In this case, I write the normal xUnit specs. This tests are as follows:
Since we have a tests, we can run them, of course, the tests should be ‘red’.
Due to the fact that the implementation of add method is empty, let’s implement it. For now, for any input parameter it should return 0.
We run the tests again, and as we can see they are green.
So let’s go to the point 1.2, this time if an input argument has a numeric value it should be return. Otherwise, we should return 0. To do this, I wrote FsCheck test. Why? It will allow us to test the method on a number of input data, and not just a single “input”. As I mentioned earlier FsCheck test run test for various inputs 100 times (this is the default value, which can be changed in the custom configuration, about which I say more in point 4). The tests are below (as we can see, to run/annotate FsCheck test we use an xUnit (annotation on the method [Fact])):
Before we get to the results of the test, we need to clarify some issues. We’ll start with the code in C#. Prop.ForAll means that test run for all data submitted in 1 argument (which could be read “for all properties do something”), for which we do the check referred to in the second argument. Worth noting here is also a way of generating input data, which looks as follows:
We can see that we generate any numeric value int and then convert it to a string to fit the input function.
The Arbitrary type means an instance that wraps the test data generator and shrinker (which is responsible for matching the data that don’t meet the condition of our check, and ‘shrinks’ it to the ‘smallest’ one which is not passing the test).
In contrast, Arb.Generate
But if we have more complex models generic version of Prop.All would not works. In F# we define parameters to a function with a valid types and FsCheck do all for us.
Now we proceed to implement the Add method. For each of the input, method should return its numeric value. Please note that the input argument to the method is a string, so we have to check if the parameter is correct. Implementation of the Add method in this stage looks like this:
The next step will be to adapt the add method in that way it could be able to accept many numbers separated by a space as an argument. For this purpose, we modified/create new one test methods to accept a list of numbers, which then are combined to a string and pass to the method. The tests are as follows:
We can see that in F# case I used several functions that were not used previously. So their and other utils functions implementations are shown below:
Modified Add method looks as follows:
So far, the number could be separated only by a space, we introduce a modification that numbers could be also splitted by a new line character.
Tests looks like this:
The modified code of an Add method:
Another point is to change the function so that it is able to accept an additional argument, so we could call a function with any delimiter. In C# case, you must write a custom data generator. So that each test was passed object with a list of numbers and a custom delimiter, which is in the form of a single string. Such generator looks like this:
The modified tests is as follows:
Modified add method looks like this:
We find, however, that the test is not green, why is this happening? Delimiter can be any string of characters, and thus the number. In the case when it is a number, and exactly the same number(s) exists in the input data array. The result of a method is incorrect. So what we have to do? We must bear in mind that the delimiter may not be a number, in this case, we modify our F# test and C# generator as follows:
C# generator: F# test:
As mentioned at point 2. I wanted to mention here how to modify the test run configuration. We catch an error just because test run on various data a few times. What to do to avoid this? You can specify a modified configuration. It looks as follows:
Parameters StartSize and EndSize define the granularity at which data generator generates data. Generators in FsCheck increase their value in small steps, eg. 1, 2, 4, 10 … etc. An important aspect is also the fact that the launch of the test with their own setup is a little different, instead Check.Quick(test) or Check.QuickThrowOnFailure(test) we call Check.One(config, test).
Penultimate thing to do is to throw an exception if we pass a negative numbers to a add method. So here are the tests:
Modified Add method looks like this:
The last point to achieve is to skip adding up numbers that are greater than 1000, so that eg. An attempt to call the Add method with two values 1001 and 1 should return 1. Test code looks like this:
In summary due FsCheck we have the opportunity to test the reaction of the data for a large range input. What we would not be able to fully check via normal unit testing eg. In NUnit or xUnit (which by the way have attributes to fire tests with specific parameters TestCase for NUnit and Inline for xUnit). With random testing we could draw a conclusion about delimiters that they can not be a number what could be impossible to achieve with conventional unit tests. The mere use of FsCheck with such a problem may seem like rifle shooting to the fly, but it has to show us the basic features and capabilities of property-based testingu and random testing using FsCheck, which has the intention to set/check properties of arguments/properties.
Thanks for reading. All source could be found on github: