sábado, diciembre 11, 2010

Hit The Road Jack and Don't You Come Back No More

Today, thanks of Neal Ford (I have met him in Devoxx 2010[lovely presentation]), I have been reviewing its presentations and I have found interesting one called "Unit Testing That's Not So Bad: Small Things That Make a Big Difference". In this presentation are showed tools for testing like JUnit, InfiniTest, Jester, ... also shows some strategies for writting your tests, and finally a real revelation, you can also test your PRIVATE methods. More often than not, you create an interface that have a public method, and then a class that implements this interface (functionality), and because of code clean you create some private methods that help public method to acquire its porpoise. And then comes test part, you write testing code for your public method, you run tests, 100% coverage, and that's all, you think you have got a perfect code, but, ... that's not true, you could have a bug to a private method that is not reflected in final result (for example a divide by zero), and because you have not write tests for your private methods, you have not though about possible results and although your code have 100% coverage, it is not perfect.

Furthermore, your public method can be an aggregation of several private methods, and would not be funny and easier to test private methods alone? I am sure you are thinking yourself, "Yes would be perfect for that class I wrote some time ago where my private methods contains 60 lines of code each one".

Imagine next example:

We need to develop a software that calculates how many egg-cups shall be required for placing "n" eggs. We have a method that returns the number of eggs (imagine that this value is returned from a complex method like connecting to webservice, connecting to database, ...), also another complex method returns the number of positions in each egg-cup.

So Class shall look something like:

public class EggCupCalculation {
private int numberOfEggs = 0;
private int numberOfHoles = 0;
public int calculateNumberOfEggCups() {
int numberOfEggs = getNumberOfEggs();
int numberOfHoles = getNumberOfEggCupHoles();
return calculateNumberOfRequiredEggCups(numberOfEggs, numberOfHoles);
}

private int calculateNumberOfRequiredEggCups(int numberOfEggs, int numberOfHoles) {
int requiredEggCups = numberOfEggs/numberOfHoles;
if(areEggsRemaining(numberOfEggs, numberOfHoles)) {
requiredEggCups++;
}
return requiredEggCups;
}
private boolean areEggsRemaining(int numberOfEggs, int numberOfHoles) {
return numberOfEggs%numberOfHoles>0;
}
private int getNumberOfEggCupHoles() {
// Complex operation like accessing database for retrieving number of egg cup holes.
return this.numberOfHoles;
}

private int getNumberOfEggs() {
// Complex operation like accessing external resource
return this.numberOfEggs;
}


public void setNumberOfHoles(int numberOfHoles) {
this.numberOfHoles = numberOfHoles;
}

public void setNumberOfEggs(int numberOfEggs) {
this.numberOfEggs = numberOfEggs;
}
}

Where both class attributes has been used for simplyfing the example, and not requiring a mock.

And Test Class:

public class WhenNumberEggCupsIsCalculated {

@Test
public void twenty_Five_Eggs_Should_Require_Three_Egg_Cups(){
//Simple test with simple logic for understanding propose
EggCupCalculation eggCupCalculation = new EggCupCalculation();

//For avoiding a complicated test, this is for teaching propose, a mock will not be used.
eggCupCalculation.setNumberOfEggs(25);
eggCupCalculation.setNumberOfHoles(12);
assertThat(eggCupCalculation.calculateNumberOfEggCups(), is(3));
}

@Test
public void twenty_Four_Eggs_Should_Require_Two_Egg_Cups() {
//Simple test with simple logic for understanding propose
EggCupCalculation eggCupCalculation = new EggCupCalculation();

//For avoiding a complicated test, this is for teaching propose, a mock will not be used.
eggCupCalculation.setNumberOfEggs(24);
eggCupCalculation.setNumberOfHoles(12);
assertThat(eggCupCalculation.calculateNumberOfEggCups(), is(2));
}
}

And as you can see 100% coverage is acquired. And now you can think, well my code is perfect, cause I have thought about two possible cases, when the division have no rest or when have rest and one more egg cup is required.

Yeeeeeees, but what's happen if one day getNumberOfEggCupHoles() returns a 0, meaning that eggs cannot be into an egg cup? Then Booooom.

I am sure that if you had tested private method calculateNumberOfRequiredEggCups() you had seen that a divide by zero could occur.

This is a simple example, imagine with real business case.

So here are three possible solutions for testing private methods, every one is free to choose the best for him.

Solution 1:

Make all methods public (or package) scope, put your tests in same package (but in different source folder) as classes, and write your tests. It is an ugly possibility you modify your development, breaking OOP principles for test.

Solution 2:

Use reflection, that big omitted thing. How about using reflection for accessing to private methods? Good, let's look code:



@Test
public void twenty_Five_Eggs_Should_Require_Three_Egg_Cups(){
//Simple test with simple logic for understanding propose
EggCupCalculation eggCupCalculation = new EggCupCalculation();

assertThat(getNumberEggCups(eggCupCalculation, 25, 12), is(3));
}

@Test
public void twenty_Four_Eggs_Should_Require_Two_Egg_Cups() {
//Simple test with simple logic for understanding propose
EggCupCalculation eggCupCalculation = new EggCupCalculation();
assertThat(getNumberEggCups(eggCupCalculation, 24, 12), is(2));
}
@Test
public void twenty_Four_Eggs_Without_Egg_Cups_Should_Require_No_Egg_Cups() {
//Simple test with simple logic for understanding propose
EggCupCalculation eggCupCalculation = new EggCupCalculation();
assertThat(getNumberEggCups(eggCupCalculation, 24, 0), is(0));
}
private int getNumberEggCups(EggCupCalculation eggCupCalculation, int numberEggs, int numberEggCupHoles) {
try {
Method m = EggCupCalculation.class.getDeclaredMethod("calculateNumberOfRequiredEggCups", int.class, int.class);
m.setAccessible(true);
return (Integer)m.invoke(eggCupCalculation, numberEggs, numberEggCupHoles);
} catch (SecurityException e) {
fail();
} catch (NoSuchMethodException e) {
fail();
} catch (IllegalArgumentException e) {
fail();
} catch (IllegalAccessException e) {
fail();
} catch (InvocationTargetException e) {
fail(e.getMessage());
}
return -1;
}


Well now you can access private method, you still have the problem, because of divided by 0, but your code does not pass all tests. That's good, you can play with parameters of private methods and find possible errors and/or reproduce strange scenarios. Ok, it is a good idea, but maybe too complicated, you must use reflection and also a lot of possible exceptions (test fail), not because your business but because of reflection. So it is not a clean solution or at least simple to read. Of course it is a valid solution.

Solution 3:

Using Groovy. Expand your mind, there is some script languages (supported nativly by JDK), that can make your life easier, and this is one case. How about that groovy file:

class WhenNumberEggCubsIsCalculatedGroovy {

@Test public void twenty_Four_Eggs_Should_Require_Two_Egg_Cups() {
def m = EggCupCalculation.class.getDeclaredMethod("calculateNumberOfRequiredEggCups",int.class, int.class)
m.accessible = true
assertThat m.invoke(new EggCupCalculation(), 12,6), is(2)
}
@Test public void twenty_Four_Eggs_Should_With_No_Egg_Cups_Should_Return_Zero() {
def m = EggCupCalculation.class.getDeclaredMethod("calculateNumberOfRequiredEggCups",int.class, int.class)
m.accessible = true
assertThat m.invoke(new EggCupCalculation(), 12,0), is(0)
}
}

Easy to understand, and easy to test, not catch sequence, not casts, not complicated expressions. If you had tested private methods (knowing that a division is performed), then you would see that a division by 0 can occurs and you modified the code for not crashing.

Now you have three valid options for testing private methods, choose the best for you. Of course this a simple example and everybody knows that this particular example is difficult that occurs in real examples, but this is only a pedagogic example.

And now it is time to think about what private methods are candidate to be tested. Not all, but these private methods that contains important/critical business code should be tested.

In future posts, I will show you how to integrate maven into groovy test, so java native junit tests and groovy junt tests are runned.

Donate If You Can and Find Post Useful