On Aug 22, 8:29 pm, Steve Freeman <
steve.free...@m3p.co.uk> wrote:
>
> ...
>
> Presumably, there are some higher-level tests to show that everything holds together.
>
Yes there are acceptance tests that tests the census representativity.
Those green tests have been useful to see that all the parts were
working well together as expected also when refactoring this code and
moving responsibilities around between classes.
> Perhaps you could write this up with code examples?
>
Here it is, a small functions composition, still it was not easy to
move away from the initial unit tests that were not really unit and
find another good enough direction.
This was the starting point:
http://www.pastie.org/2427601
The RepresentativeSample class that have a PanellistsCount property
(here is important only the number of panelists, every single panelist
item/entity is of no interest here) and the representativity criteria
of that sample that can be by gender, by age group or by main region.
Note the ? in the declaration of the properties, in C# it means that
the property can be null, it means that the sample has not been
partitioned with that criteria.
Look at the functions CreatePartitionByAgeGroup(),
CreatePartitionByGender() and CreatePartitionByMainRegions(): they all
call the private CreatePartition() function passing a
PartitionFactoryFunction as argument. This is a simple function
composition.
The unit tests that I initially wrote on CreatePartitionByAgeGroup()
are very similar to the one on CreatePartitionByGender() and on
CreatePartitionByMainRegions(). They are somehow all testing the
CreatePartition() function and how it is used. So they do not look
very "unit" to me.
Because of this I tried to extract CreatePartition() function,
initially in another class as a public instance function and try to
test it separately from RepresentativeSample.
The challenge here was that the CreatePartition() function accept as
argument another function. A simple function composition, not common
in OO code, instead something common in functional code.
So here is how I extracted CreatePartition() function:
http://www.pastie.org/2427676
Note that:
- the class and the function is generic to avoid the dependency to
RepresentativeSample,
- the class implements one interface and the function is implemented
by an instance method, this enable me to mock it when I test the
interaction between RepresentativeSample and the Create function.
- the delegate PartitionPartFactoryFunction is not needed/mandatory
(the Funct actions and lambda expressions in C# work well without it,
still to me it help to better describe with names the expected
function parameter signature. Maybe is just because I'm not enaugh
into the functional paradigm, time will tell.
After extracting the Partition function here is how
RepesentativeSample looks like:
http://www.pastie.org/2427723
The partition functions CreatePartitionByAgeGroup(),
CreatePartitionByGender() and CreatePartitionByMainRegions() now
delegate the partition to the extracted CreatePartition() function
(now Partition<T>.Create).
The new/interesting part for me is in the unit tests of the
interaction between RepesentativeSample and the CreatePartition()
function (now Partition<T>.Create). Is the test with a mock of the
function call and also of the functions composition, that looks to me
to be something related to the functional paradigm:
http://www.pastie.org/2427792
Note that the mock MockPartitionBuilder here is a custom hand-written
mock that expect the function call (to Create) and also that expect
that the function passed by RepesentativeSample as argument for
PartitionFactoryFunction behave as expected (this is specified by the
expectedPartitionFactoryFunctionResult value).
And finally these are the tests for the extracted CreatePartition()
function (now Partition<T>.Create) :
http://www.pastie.org/2428047
Tests are about the expected results of the partitioning and the
commutativity property that a partitioning function should have.
Note:
- at the end of the file the "identity" function that is passed as a
stub to the Create function (was CreatePartition) to easily check the
use/composition of the passed function work as expected
- initially these tests needed to be repeated for all the 3 methods
CreatePartitionByAgeGroup(), CreatePartitionByGender() and
CreatePartitionByMainRegions(), now this is no more needed
- also the test of the commutativity property required to test all the
combination of the 3 functions now this is done only once for
Partition<T>.Create. A sign that now responsibilities are better
assigned/organized.
I had other similar refactoring and unit test/mocking approach on
other classes related to the same feature and it worked equally well
there for me. The 3 partition functions defined in RepesentativeSample
at the end were moved in the RepesentativeSamples class that compute
the partition both for a single sample and for a set of samples (can
compute the partition of another partition) so RepesentativeSample end
up to be a simple read-only value-object.
Any comment/feedback/suggestion is welcome.
Luca