Felix Dahlke is a software developer particularly interested in games and open source. He writes most of his code in JavaScript, C++ and Python. Felix is a DZone MVB and is not an employee of DZone and has posted 16 posts at DZone. You can read more from them at their website. View Full User Profile

Unit Tests Versus Code Quality

10.03.2012
| 5027 views |
  • submit to reddit

Do unit tests improve code quality? Some famous consultants might disagree, but I think they don’t. Testable code isn’t automatically better code. Depending on the capabilities of your language, it’s probably worse.

Now don’t get me wrong, unit testing is a good thing. But I think we need to realise that we’re often making a trade-off between simplicity and testability. To me, simplicity is the most important factor of code quality, but many people lean towards testability and are very successful with that. Maybe a complex, well tested system is better than a simpler system with less test coverage, I can’t answer that. But you can’t have both, at least not in static languages.

Let me explain what I mean: In a unit test, you’re testing a small part of your system in isolation. You’re ensuring that a single module, class or function, works as expected, now and in the future. You only test the unit as it can be used from the outside, and that’s good, because its implementation details shouldn’t matter to the rest of the system. But it’s also a problem.

It’s a problem because you often have to actually change the unit to make it testable. There are plenty of examples for this, but I’ll stick to one in this post: You’re testing a unit that uses a random number generator. Since the behaviour of the unit will be different every time you use it, you need a way to take control of that random number generator in order to test it reliably.

If your language supports object-oriented programming, the common approach is to introduce an interface for the thing you need to control and inject it from the outside. Let’s say we’re creating a RandomNumberGenerator interface and pass an instance of it to our unit. You can then create a fake implementation that does just what you want, and pass that one to the unit from the tests. Now you can make sure that your unit works fine for various random numbers.

However, we have just added to the system’s complexity. We have created a facade for a random number generator, which is very likely already available in your language’s standard library. Anyone working on your code base will now have to know that your facade has to be used instead of the standard method. We have also introduced an interface that doesn’t make much sense right now: Ignoring the tests, there is only one random number generator – why have an interface if there is only one implementation of it? That’s nothing but unnecessary code other poor souls will have to wrap their head around. Maybe you even introduced a dependency injection framework – this is going to make your code base a lot more complex.

Languages that support monkey patching (most dynamic languages, e.g. JavaScript) are an entirely different matter: You can simply rebind the dependencies in your tests. I think that’s how testing is supposed to be: We should just write simple and clean code and be able to test it, without having to think about how to test it and what trade-offs to make. But static languages are still around, popular, and the only option for many applications, so I guess we will need to make such trade-offs for quite some time. Let’s at least be honest about it: It sucks.

Published at DZone with permission of Felix Dahlke, author and DZone MVB. (source)

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

Comments

Jilles Van Gurp replied on Thu, 2012/10/04 - 2:02am

Tests do not automatically improve code and there are a lot of bad tests out there that merely reflect how bad the code is they are testing. However, writing good tests requires the underlying code to have a certain level of quality as well. A good indication of quality is the effort it takes to write a good test for it. If that hard, the underlying code sucks. 

Now techniques like dependency injection are actually a good thing to use, even in scripting languages. I disagree that this introduces a lot of complexity. Usage of dependency injecting frameworks is entirely optional. All you need to do is use parameters and refrain from mixing plumbing code and logic. That's exactly the kind of thing that makes stuff easy to test. Javascript (or any language with support for closures) makes this super easy as well: you don't even need a lot of boiler plate.

Monkey patching may sometimes be necessary but you should be careful with it. Just because you can doesn't mean you should. I personally hate it when I don't stand a chance of seeing at a glance of an eye what code does because somebody redefined the semantics of everything I thought I knew. That's the very definition of bad code. Multiple inheritance, templates, macros, and operator redefinition are what gave C++ a bad name. That enabled people to write code that didn't do what it appeared to do and a lot of misguided programmers did an enormous amount of damage doing so, thus causing huge maintenance headaches.

I think people over emphasize static vs. dynamic btw. I think things are a lot more murky these days. There are some statically compiled languages (e.g. Kotlin, Scala, Mirah, Go) that pretty much look and feel like scripting languages and there are scripting languages that look like Java's retarded little brother (e.g. php, seriously makes me want to byte my arms of every time I go near it).Basically, the techniques that people use to make javascript execute faster (just in time compilation, dynamic optimizations) are exactly the same techniques that are used in compilers and virtual machines for C++ and Java. In the end it's all native code that executes and it is not that interesting when that is generated.

For all its qualities, javascript still lacks good testing tools and associated practices. In my view a person having to click reload in a browser doesn't qualify as automated testing. Yet, this is what most javascript testing tools require you to do. Is it really so hard to untangle your logic from the browser and set up a build server that runs tests every time you check something in? Why the hell does that have to be so painful for javascript? A lot of the misery involved with cross browser testing is self inflicted. Yes there are differences between browsers and if you were able to test for it automatically, it wouldn't be such a big deal.

Lloyd Shove replied on Thu, 2012/10/04 - 5:26am

I understand your point and you're right, given a well coded simple system you may need to add an amount of complexity to, in this case, unit test.

You're not  right about needing to introduce a facade interface, which will increase complexity.  You can test using one of the mocking frameworks like Mockito against a concrete class (e.g. java.util.Random) so no need to introduce a facade.

Anyone introducing dependency injection to make testing easier deserves the pain they get.

 

Rogerio Liesenfeld replied on Thu, 2012/10/04 - 7:04am

I think it's important to distinguish between two different forms of testability: intrinsic and extrinsic. The first is the one you really want to care about, as it is deeply related to the simplicity and maintainability of the codebase. The second, in the end, is merely an illusion. Even with languages that do not directly support "monkey patching", it *is* possible to write unit tests without introducing changes such as facades or dependency injection. This is achieved through the use of libraries specifically designed for the purpose of testing code in isolation from its dependencies (so called "mocking" or "faking" tools). For example, we have PowerMock and JMockit (my own tool) for Java, and TypeMock Isolator, JustMock, and MS Fakes for C#. (There is even a version of TypeMock Isolator for C++!)

In fact, even the languages that do support monkey patching have their own mocking libraries, as directly using their code patching abilities is cumbersome when compared to using a mocking/faking API.

So, if you do change your code to make it more testable, make sure you do it for the right reasons, not because you don't know (or don't want to use) a proper solution for a particular unit-dependency isolation problem.

Jilles Van Gurp replied on Thu, 2012/10/04 - 8:48am in response to: Rogerio Liesenfeld

You seem to be confusing a few things. All dependency injection is, is sensible/basic code hygiene. You don't need facades, frameworks, mocking, or whatever (though they certainly help).

This is dependency injection:

someMethod(WhateverType dependency) {

  dependency.useIt();

}

I.e. isolate the use of a dependency from its creation so that you can test in isolation, which is what unit testing is about. When you test the code, you get to provide the dependency (or a mock), which is usually what you'd want.

This on the other hand is hard to test code:

someMethod() {

hardwiredDependency = new WhateverType();

hardwiredDependency.useIt();

}

You are stuck with the hardwired dependency. Mocking doesn't save you here because there is nothing to mock. You might do some crazy stuff trying to redefine the behavior with e.g. aspect oriented programming, reflection, tricking the classloader into loading some alternate implementation, or indeed monkey patching. But why rely on that when you can simply move the dependency to a parameter after which testing is easy. The only reason I can think of to make life that hard is if testing is an afterthought.

Rogerio Liesenfeld replied on Thu, 2012/10/04 - 9:27am in response to: Jilles Van Gurp

My reply wasn't about dependency injection, but about the fact that testability does not conflict with code quality. On the contrary, they go hand in hand. High-quality code may or may not use DI, but it should be relatively easy to write unit tests for it, with or without DI.

The example code which you claim to be hard to (unit) test is *not* hard at all, provided you can easily and cleanly isolate the tested unit from the implementation of its dependencies. As I said before, this can be done for most (all?) modern programming languages.

For example, you can write the following unit tests for "someMethod()", using two popular mocking APIs freely available for Java.

<code>

    @Test

    public void testSomeMethod_usingPowerMockito() {

       WhateverType mock = mock(WhateverType.class); 

        PowerMockito.whenNew(WhateverType.class).thenReturn(mock); 

        new SomeTestedClass().someMethod();

        Mockito.verify(mock).useIt(); 

    } 

    @Test

    public void testSomeMethod_usingJMockit(@Mocked final WhateverType mock) {

        new SomeTestedClass().someMethod();

        new Verifications() {{ mock.useIt(); }};

    } 

</code>

I  believe the unit tests above qualify as easy...

Orlin Gueorguiev replied on Thu, 2012/10/04 - 10:04am

You can always use an aspect orientated approach (AspectJ/Spring AOP, EJB 3.1 has aspects as well), where you can override a given method. In Your example with the random number generator, you need to redefine only the return value. Still, taking Lloyds' approach is better.

Comment viewing options

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