Thursday, July 23, 2009

Is ignoring private access in Groovy evil?

The world's worst kept secret has got to be that Groovy ignores the private access modifier in code. You're free to access and invoke private methods, fields, and classes. A well known speaker called this "feature" insanely useful for unit testing.

I'm adding that it's also insanely evil. But it's not both insanely useful and insanely evil... it's either, depending on how you use it. I've spend all night in Gimp making a freakin' sweet graph showing the continuum, but before the big reveal we need to review some vocabulary.

The most useful addition from xUnit Patterns to the testing community is the classification of input and output as either direct or indirect. The easiest methods to test accept direction input (a method parameter) and return direct output (a return value). Think about testing Math#min(int, int). Damnably easy. Indirect input is when your system under test can't be fed input that it uses directly but can be fed an object that can locate that input. Thank about testing a class that uses EntityManager.find(Class, Object) to look up a row on the database. You can provide a mock entity manager that indirectly gives the system input. It's not too difficult but a lot harder than testing Math#min(int, int). Indirect output is when you cannot make assertions about the return value of the method to verify that it worked correctly. Instead you have to look at what side effects the method performed. Any time you are verifying mock method calls, or looking up that correct values were set back on the input values, then you are dealing with indirect output. This is the hardest to test.

So here is testing continuum for using Groovy to ignore private access modifiers:

This image took me like 4 hours to make so don't you dare laugh. Here's a useful key:

Not Using - Clearly, it is best not to use this feature. It's how Jesus would code. Yes, I'm serious. Jesus.

Indirect input in 3rd party library - So you're using a library that is hyper encapsulated and difficult to mock (like the JDK?). The library is unlikely to change frequently and you control when it changes. Sure, setting private values to provide your test with indirect input is useful. It'd be nice if there were more testable options, but at some point you have to wash your hands, stop trying to do the right thing, and just make a decision. A little like Pontius Pilate.

Indirect input in you own objects - Ignoring private in your own libraries can be insanely useful for getting some tests written quickly and out the door. But you're just creating tight coupling to the implementation of other objects... and you control those objects! Clearly there is a better option, and you have the control to perform that option. On an evil scale, it's a little like Saint Paul. Huh? A saint? That's not evil? Let me remind you: Paul killed a dude. With a rock. KILLED HIM. Yeah, go look up how Saint Stephen died. So, like Paul, you are clearly violating your principles in this scenario, but the situation is redeemable. Use private access cautiously, but then come back later and do the right thing. Bad decisions now won't stop you from being canonized later.

Indirect output in 3rd party library - The iteration is late, your test coverage is low, and you're stuck integrating with a shoddy 3rd party API where every method returns void. Slam some tests together and violate private access to write a fragile test coupled to implementation that looks freakishly similar to the production code. Did I just reimplement the system under test in mocks? Yup. What am I really testing? Nothing. Like Judas Iscariot, you've sacrificed your design principles for thirty pieces of silver. In 3 months, when maintenance is a nightmare, deny 3 times that you had anything to do with the code. If that fails, blame it all all on a contractor and move on.

Indirect output in your own objects - Dude c'mon. The only way to test your system that you wrote is to access private fields of objects you own? I don't buy it. This is where the difference between TDD and Test Always shows itself. I challenge anyone to evolve a system using test first that requires this sort of shenanigan. I just don't see how focusing on behaviors and testability would ever leave you like this. Whereas, trying to write tests for code after the fact might. Cain killed his brother and then denied it. That's pretty evil, just like accessing private fields on objects you own. You're given a great tool like Groovy for testing and this is how you choose to wield it? Like Abel, Groovy deserves better. When we catch you doing this, don't try to deny it. We'll know you're lying and we won't forget.

Indirect output in system under test - Old school Satan bad. This is just unjustifiable and with no benefits. What can your test possibly assert? That the code you wrote performs the way you wrote it? On a continuum of useless tests, that's like Pet Rock useless. And before you say, "I'm just laying down some unit tests so that I can refactor later," let me add that almost any refactoring you perform is bound to break the tests. Have fun with that. This usage just seems evil to me.

I don't often ask myself "What would Jesus do?" I try to live my live more like Pilate. I try my best but, hey man, sometimes stuff happens. Don't get hung up on it. Although, I'll admit to doing some pretty nasty stuff in the past, so a poll of my co-workers might reveal I'm more in the Paul range than I'd like to admit. How about you?

1 comment:

Vc4IZEkU0sxOgncQNzHWzavKew-- said...

Evil is definitely the right word... Check out this twisted example I came up with in October 2007: