I'm an Agile and Lean Strategist specialised in coaching and managing the transformation of IT departments, from startups to enterprise-scale organisations, to highly efficient, productive and energised environments. I also have experience as senior development manager and architecture governance in large enterprises, especially in the finance sector. Marco is a DZone MVB and is not an employee of DZone and has posted 26 posts at DZone. You can read more from them at their website. View Full User Profile

Incompatibility between TDD, Mocking and Static Methods

04.05.2011
| 5132 views |
  • submit to reddit

Today I installed Jenkins CI and started building PODAM. I write all my code using TDD. The public API in PODAM offers a single method for now:

POJO myPojo = PodamFactory.createDummyPojo(POJO.class);

I decided to provide developers with a very simple-to-use API . For a complete list of PODAM requirements, please refer to my previous post.

So far I just wrote a single unit test which led to this piece of functionality:

     /**
* Generic method which returns an instance of the given class filled with
* dummy values
*
* @param <T>
* The type for which a filled instance is required
* @param dto
* The name of the class for which an instance filled with values
* is required
* @return An instance of <T> filled with dummy values
*
* @throws PodamMockeryException
* if a problem occurred while creating a POJO instance or while
* setting its state
*/
public static <T> T mockDto(Class<T> dto) {

try {
return dto.newInstance();
} catch (InstantiationException e) {
throw new PodamMockeryException(
"An exception occurred while instantiating " + dto, e);
} catch (IllegalAccessException e) {
throw new PodamMockeryException(
"An exception occurred while instantiating " + dto, e);
}

}

 

The unit test is as follows:

@Test
public void testSimpleDtoGraphGetsSet() {

SimpleGraphTestPojo dto = PodamMocker.mockDto(SimpleGraphTestPojo.class);
Assert.assertNotNull("The object cannot be null!", dto);

}

 

This is obviously only the beginning but following TDD practices it was my first passing test.

I then decided to use the Jenkins CI Emma plugin to check code coverage and was really surprised when I got 50% at class level. Upon investigation the Emma plugin showed me that I didn't test the branches for InstantiationException and IllegalAccessException. Static methods cannot be mocked since these are resolved at compile time.It turns out that either one has to write an interfaced service (maybe exposed as a Singleton) or that one has to give up testing every single branch of a static method when such branch throws Exceptions difficult to reproduce.

Nowhere is written that service classes (e.g. non instantiable classes with static methods) are a bad thing; on the contrary, Effective Java 2nd Edition, in Item 4, suggests that such utility classes have their uses. As a developer, if I had to decide between:

POJO myPojo = PodamFactory.createDummyPojo(POJO.class);

and

PodamFactory factory = PodamFactoryImpl.getInstance();//Singleton

POJO myPojo = factory.createDummyPojo(POJO.class);

I'd prefer the former; the latter looks a lot of boilerplate code. 

So it turns out that if one wants to use utility classes which expose static methods, and such methods throw internallly exceptions difficult to reproduce, one has to give up full coverage. Between having 100% coverage and ugly syntax on one side and 50% coverage with easy-to-use syntax on othe other, I chose the easy syntax.

 

From http://tedone.typepad.com/blog/2011/04/incompatibility-between-tdd-mocking-and-static-methods.html

Published at DZone with permission of Marco Tedone, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Francois Roland replied on Tue, 2011/04/05 - 2:14am

Hello Marco,

In this particular case, if you write a class (only for test purpose) which doesn't have a no-argument constructor and pass it as argument to your method, it will throw an InstantiationException. The same may occur if your class argument is an abstract class, an interface, an array class, a primitive type, or void. These are anyway cases that you probably want to test.

For the IllegalAccessException, create another class to pass as argument that is private or has a private no-argument constructor.

That way, you can achieve a 100% coverage of your method. But even then, 100% coverage as said by emma (or cobertura, jcoverage or anything else) doesn't mean that you have functionnally tested everything.

Have a nice day.

Marco Tedone replied on Tue, 2011/04/05 - 3:22am in response to: Francois Roland

Hi Francois, thank you for your pointers. You gave me certainly some negative tests to include in my suite and some really good ones I might add!

Andras Milassin replied on Tue, 2011/04/05 - 7:54am

Good software design is not (always) testability. Sadly static methods are the arch enemies of testing. From this point of view Effective Java is a really great book but some of the elements actually should be considered as anti-patterns. If you want to achieve 100% code coverage and easy testability you have to adhere other patterns.

Check Misko Hevery's (Google) guide on how to write testable code: http://misko.hevery.com/code-reviewers-guide/

With the use of PomerMock you can test static methods: http://code.google.com/p/powermock/ Mocking is a great thing but if you are developing in TDD you should try to avoid writing static methods.

Alessandro Santini replied on Tue, 2011/04/05 - 4:32pm

Ciao Marco,
I frankly never sacrifice testability in favor of ugly syntax. Sure, having a tool reporting 100% coverage does not automatically imply good test cases and therefore quality software but, having said that, I do not see how your example could be considered ugly syntax and a lot of boilerplate code.

As you say, that is only the beginning of a test case; if you take it as it is now, I surely agree with you that the singleton call would increase your SLOC count by 100%; but let me also consider that:

  • The more code you add to the test case, the more irrelevant that tiny call becomes;
  • You can cleanly move that line of code in your @SetUp method for extra clarity, hence removing the lot of boilerplate code to another place;
  • You might later decide to create multiple variants of the PodamMocker; you might decide to adopt subclassing or a Strategy Pattern, but either way static methods would clearly prevent you from such extensions, imho.

My € .02.

Marco Tedone replied on Wed, 2011/04/06 - 1:43pm in response to: Alessandro Santini

Ciao Alessandro,

While writing the API I'm thinking of making developer's life easier. Since I write quite few tests myself everyday at work I tried to think what I would prefer if I had a tool like PODAM: additional syntax to retrieve an instance of my service class in order to invoke a method or a simple invocation of a static method.

And I don't feel alone in this choice; apart from the book I mentioned above, I'm thinking of tools such as EasyMock (or PowerMock), or indeed JUnit. The EasyMock.create... or Assert.assert... methods are abundant and very convenient. 

I can only imagine how annoying would be for me, everytime I want to assert some state, writing: 

Asserter asserter = Asserter.getInstance();

asserter.assertNotNull("message", obj);

Podam syntax goes exactly in the same direction. I think developers will enjoy more a syntax like: 

Pojo myPojo = PodamFactory.createMock(Pojo.class);

It's actually true that by writing more code the code coverage goes up. With PODAM being now in its draft release, I'm proud to announce that overall coverage reached 80%, the target I set for myself.

Happy technology.

 

Shoaib Almas replied on Sat, 2012/08/25 - 6:02am

Another thing you can do (if you're not willing to refactor your code) is to simply use PowerMock which allows you to mock static methods using an EasyMock or Mockito like syntax.

Java Forum

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.