Friday, October 23, 2015

Closed for Permission, Open for Forgiveness

I used to think that the pattern I'm about to advocate here was unforgivable. Now I'm not sure - a side effect of test-infection.

I'm assuming OO here along with SOLID and DRY, and using the word "mock" because it's shorter than "test double", but not excluding "fake".

The controversial assumption: that tests are first class citizens and are therefore not forbidden to have an impact on production code.  In mechanical and electronic engineering, products are built to run "self-tests" long after they leave the factory: these tests are an integral part of the product and must be accommodated. Jordi LaForge ran Level 3 diagnostics in the middle of critical missions.  Freakin' Level 3, for Pete's sake! No controversy there.  But in software?

Suppose you have an object A that uses an object B. You want to verify with a test that A uses B appropriately. (Actually, because you're a smart programmer, you test-drive A to do the right thing.) You inject B as a constructor argument for A (no setter, because you understand the value of immutability).

So C, which instantiates A (whether C is a factory or a POLO [Plain Old Language Object]) now has to know how to provision/acquire/instantiate a B.  Maybe C is a container or a Springy framework, in which case you figure this is just part of its job.  But even in that case there's a dependency, no matter how cheap it is to create (say, with attributes/annotations).

If B is not part of a strategy pattern - if there's no "production reason" for injecting it - you're only doing it for the test.

What I've started doing is implementing a test-only constructor on A that takes a B argument so that I can inject a mock B. The production constructor instantiates B.

Frameworks like Hibernate make you create a default constructor so they can use reflection to set what should otherwise be private members.  That's nasty. Then there are the "beans" that require setters for every instance member when you really wanted an immutable value object. That's obscene.

Just sayin' - the test-only constructor is a microevil because nothing else has to know about it or use it.  Its visibility can be minimal, as long as tests can get at it.  For example, in a Java project where test source resides in a package structure parallel to but completely separate from production source, the test-only constructor can have default (≅ package) visibility. Similarly: assembly visibility in C#.

I'm not losing sleep over it.

No comments:

Post a Comment