Platinum Partner
groovy

Does Groovy code require more tests than Java code?

Many of the complaints I've heard directed at Groovy and Grails derive from the same issue: the compiler doesn't pick up type errors. People worry that simple typos will make it into production and that they'll be less productive due to MissingMethod and MissingProperty exceptions popping up when they run the application.

The standard answer to this is a simple one: write tests for your code. In fact, prefer test-driven (or test-first) development. Yet this doesn't seem to satisfy some people. "What projects do you know that have 100% test coverage?" One or two that have close to 100%. But are the rest reliable? No, not even the Java-based ones. Any code that isn't tested has a significantly high chance of being buggy. How can this be a serious argument against dynamic languages?

That's not to say I think dynamic languages should take over from static languages. Both have their place and deciding which to use for any given task will often come down to personal preference. They both have their strengths and weaknesses, but a discussion of those will have to come another time.

Too much testing!

Another argument I've heard is that dynamic languages require more testing because you have to explicitly test for type errors that would otherwise be picked up by the compiler. I think this is frankly rubbish. What do we do when we're testing? We're checking that code behaves as expected given certain inputs. If there are any type errors in the code under test, you won't see the expected behaviour.

Let me demonstrate. Say we have a Grails controller MyController with a corresponding unit test. The test starts simply:

package org.example

class MyControllerUnitTests extends grails.test.ControllerUnitTestCase {
    void testIndex() {
        controller.index()
    }
}

All we do is invoke the index action on the test controller. Now see what happens when we run the test against this controller code:

package org.example

class MyController {
    def index = {
        def id = parms.id
        render "You have reached ID ${ids}"
    }
}

If you run the test in SpringSource Tool Suite, you'll see something like this:

As you can probably tell, the test is failing because the variable parms does not exist. Not surprising since this was a deliberate typo. Once you correct that typo, the test fails on the next one, ids. Change that to id and the test is now passing.

So, even though the test simply executes the action and doesn't even bother checking the results, we get feedback on missing properties and methods. Granted, the feedback isn't as instantaneous as you get in an IDE with a statically typed language, but in my experience it's not a huge hit on productivity. And while we're on IDEs, I know that both STS and IntelliJ IDEA will underline properties and methods it can't resolve, so you would probably notice that parm and ids were mistyped before running the test.

This doesn't really do much for my argument yet because I've just shown that you need an "extra test" to get the same checks you'd get with a static language. But remember that the aim of a test is to ensure that code behaves as it should, so we should check the action renders the string we expect! That's easily done:

package org.example

class MyControllerUnitTests extends grails.test.ControllerUnitTestCase {
    void testIndex() {
        controller.params.id = 10
        controller.index()

        assertEquals "You have reached ID 10", mockResponse.contentAsString
    }
}

The test still passes, but it now checks that the action is rendering the appropriate string to the response. The key point I want to make here is that the test is no different than it would be if we were testing Java code, yet it will still pick up typos.

What about type-checking?

I may have justified my position when it comes to missing methods and properties, but what about real type errors, such as using a string where an integer is expected or vice versa? This is an interesting question because in dynamic languages, it's typically the wrong one to ask. How can you have a type error in a dynamically typed language? You might have some code like this:

def n = 100
n = n.substring(1)

which you may see as a type error (a number is being treated like a string), but as far as the language is concerned all we have is a missing method on the value of n. This is important because you could very well add the method substring() to integers, in which case the code would work:

Integer.metaClass.substring = { start ->
    return delegate.toString().substring(start)
}

def n = 100
n = n.substring(1)

In other words, "type errors" in a dynamic language are simple missing method or property exceptions, which I've already demonstrated will be covered by your normal tests (without any extra shenanigans).

I would stop there, but that isn't the whole story when it comes to Groovy. After all, it has static types! It also frequently interfaces with (statically typed) Java code. How does this affect our testing? Ah, I wish there was a simple answer to this. In some ways, passing the incorrect type to a method will be picked up. Try running this script:

def someMethod(String str) {
    println "Some method: $str"
}

someMethod(100)

Boom! You'll get a missing method exception because Groovy can't find an instance of someMethod() that takes an integer. Unit tests can handle cases like this, but in practice such problems raise their head once you start wiring your objects together and they start interacting with each other. At that point, you have to start thinking about integration tests - a topic for another time.

Have I convinced you?

The main thrust of this blog post has been to highlight that your bog standard unit tests, which you can run easily from your IDE, will pick up those typos and "type errors" you're worried about without you having to do any extra work. All you need to do is focus on writing tests that check the behaviour of your code. It's not trivial to write thorough tests, but that's independent of the language you use.

Something else you should bear in mind is that It's a lot easier to test code written in a dynamic language because you don't have to always create objects of the correct type - they just have to have the appropriate methods and properties. So dynamic languages both strongly encourage you to adopt best practice and make that best practice easier.

Despite all this, I understand that it can be difficult in many environments to write tests for code, let alone write tests before writing any code. But I think this is a problem of culture and habit. Once you get used to writing tests, and then writing them first, the whole process becomes easier. You then find you have more confidence in your code. I can only strongly recommend that everyone takes any opportunity they can to try this approach out and get used to it. You never know, the managers may even come round to realising it's a good idea!

From a post on the author's blog

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}