Testing Recipes, Part 2 – Utility methods

From part 1…

Should the testing frameworks or methodologies used, influence the design of the implementation code? I’m sure most would respond in the negative, but sometimes this is very hard to avoid. I have been attempting to overcome some of this influence by finding ways to test code that I had previously refactored to make it more ‘testable’. To achieve this, I have been building on use of the standard Mockito syntax, to apply the extensions provided by PowerMock (with Mockito).

Next up, private static utility methods.

When writing a service, as the complexity increases, it is useful to extract more services to provide sub-functionality. This approach is commonly the best way to extract inner functionality, but sometimes this functionality is very contained and local and does warrant a new service. In these scenarios, one will often start to refactor the code to produce utility methods.

Take the follow example: I wish to make a service to provide string padding (I have tried to simplify the context of this example, so excuse the unrealistic service). This service is not supposed to be a multi-purpose padding utility, but instead needs to provide a restricted functionality to its users. Here’s the interface:

public interface Padder {

	public String padWithSpaces(String text, int size);

	public String padWithHashes(String text, int size);

	public String padWithDashes(String text, int size);

}

A padder will pad the supplied text to the indicated size using spaces or hashes. So how do we go about building up the implementation using tests? Notice that the methods provide similar functionality but with varying initial configuration. We could start with the first method and explore all the edge cases with tests:

anEmptyStringInputWillReturnAStringOfSpacesWithALengthOfSize()
aSizeLongerThanTheLengthOfTheStringReturnsASpacePaddedStringUpToTheSize() 
aSizeShorterThanTheLengthOfTheStringReturnsAStringTruncatedToALengthOfSize() 
aSizeEqualToTheLengthOfTheStringReturnsTheSameString()  

An then we test the next method, where the first two tests can be ‘copied’.

anEmptyStringInputWillReturnAStringOfHashesWithALengthOfSize()
aSizeLongerThanTheLengthOfTheStringReturnsAHashesPaddedStringUpToTheSize() 

and again for the final method

anEmptyStringInputWillReturnAStringOfDashesWithALengthOfSize()
aSizeLongerThanTheLengthOfTheStringReturnsADashesPaddedStringUpToTheSize() 

Often this creeping addition of virtually identical tests happens as a service gets extended over time and often the duplication is missed.

My suggestion is that when the common functionality is identified through refactoring of the implementation, we address this common functionality at the test level (whilst considering if a separate service might be the cleaner route).

Testing private static utility methods using PowerMock

Let us assume that we intend to refactor the service to provide an internal private static utility method to manage the core functionality, as follows:

public class PadderImpl implements Padder {
	
	public String padWithSpaces(String text, int size){
		return pad(text, " ", size);
	}

	public String padWithHashes(String text, int size){
		return pad(text, "#", size);
	}

	private static final String pad(String text, String padding, int size) {
		//pad the string with a supplied padding
	}

}

Note that we want to keep the utility method private because we do not intend to expose this service to our users as it would complicate the interface.

We can now directly test the utility method, which is purporting to provide a generic padding service. We can use PowerMock’s WhiteboxImpl.invokeMethod() method to invoke the private utility method in the tests:

import static org.junit.Assert.assertEquals;
import static org.powermock.reflect.internal.WhiteboxImpl.invokeMethod;

import org.junit.Before;
import org.junit.Test;

public class PadderImplTest {
	
	private Padder padder;
	
	@Before
	public void setUp(){
		padder = new PadderImpl();	
	}
	
	@Test
	public void anEmptyStringInputWillReturnAStringOfPaddingWithALengthOfSize() throws Exception {
		String result = invokeMethod(padder, "pad", "", " ", 1);
		
		assertEquals(" ", result);
	}

	@Test
	public void aSizeLongerThanTheLengthOfTheStringReturnsAPaddedStringUpToTheSize() throws Exception {
		String result = invokeMethod(padder, "pad", "text", "*", 8);
		
		assertEquals("text****", result);
	}

	@Test
	public void aSizeShorterThanTheLengthOfTheStringReturnsAStringTruncatedToALengthOfSize() throws Exception {
		String result = invokeMethod(padder, "pad", "sandwich", "*", 4);
		
		assertEquals("sand", result);
	}
	
	@Test
	public void aSizeEqualToTheLengthOfTheStringReturnsTheSameString() throws Exception {
		String result = invokeMethod(padder, "pad", "sandwich", "*", 8);
		
		assertEquals("sandwich", result);
	}
	
	@Test
	public void paddingWithALongStringCausesThePaddingToBeTruncatedIfNecessary() throws Exception {
		String result = invokeMethod(padder, "pad", "$", "abc", 5);
		
		assertEquals("$abca", result);
	}

}

We now have a padding utility service that we can trust as tested within our service. Next we need to verify the behaviour of each of the service methods. This can now be done in terms of interaction with the utility method. Testing our static method requires the test to be altered to be run as a PowerMock test and to prepare the class with the static method using PrepareForTest:

import static org.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
import static org.powermock.reflect.internal.WhiteboxImpl.invokeMethod;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest(PadderImpl.class)
public class PadderImplTest {

We can now add the final tests:

	@Test
	public void padWithSpacesCallsPadWithASpace() throws Exception {
		mockStatic(PadderImpl.class);
		
		padder.padWithSpaces("text", 10);
		
		verifyPrivate(PadderImpl.class).invoke("pad", "text", " ", 10);
	}
	
	@Test
	public void padWithHashesCallsPadWithAHash() throws Exception {
		mockStatic(PadderImpl.class);
		
		padder.padWithHashes("text", 10);
		
		verifyPrivate(PadderImpl.class).invoke("pad", "text", "#", 10);
	}

mockStatic() sets up our class for static verification, and using the verifyPrivate method, we can verify that the private static utility method is called correctly.

Adding an additional public method is as simple as describing only its interaction with the utility method, rather than copying the same set of more complex padding tests:

	@Test
	public void padWithDashesCallsPadWithADash() throws Exception {
		mockStatic(PadderImpl.class);
		
		padder.padWithDashes("text", 10);
		
		verifyPrivate(PadderImpl.class).invoke("pad", "text", "-", 10);
	}

One of the advantages with this approach is that refactoring to extract a service (where previously we had used a utility method) is much easier. This is because the tests reflect the service-like behaviour of the utility method rather than the black-box behaviour of the public methods.

There are problems with this approach however. If one is not careful to treat the utility method as ‘service-like’ then the coverage could be compromised and bugs introduced. It is not the way to test any internal method and it could be particularly dangerous if used to test entirely at a method-level. Generally speaking, the service should be treated as a black box, but occasionally it pays off to compromise and test internal method calls.

Advertisements