Peter has posted 1 posts at DZone. View Full User Profile

Does Groovy code require more tests than Java code?

04.15.2010
| 9897 views |
  • submit to reddit

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

Published at DZone with permission of its author, Peter Ledbrook.

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

Comments

Ricky Clarkson replied on Fri, 2010/04/16 - 4:26am

Even with 100% coverage, as I'm sure you know, your tests will almost never be perfect. Given two if-else statements in a method, and 100% coverage, you might only be testing what happens when the first and second both pass, or both fail, and omit half the possible flows.

Types are checked more quickly than unit tests are executed, so why not use them AND tests? Look at QuickCheck (or ScalaCheck, or fj.test in Functional Java), which uses types to drive the generation of test data.

Also, once you start using types well, you find that they drive your thinking, and so you start using them in ways that make your code simpler to reason about (no need to think about what type map['foo'] returns; it tells you), and you get all that nice autocompletion.

Rephrasing every type error as a missing method seems dishonest to me. You (not actually you) really did try to close a string as if it were a file by accident, it wasn't that the method 'close' is missing on strings.

Artur Biesiadowski replied on Fri, 2010/04/16 - 10:10am

I personally don't think that main benefit of static type systems is about compile (or even write)-time syntax error feedback - even if it is quite nice. For me, main productivity boost is from things like autocomplete/type browse/jump to definition/proper refactoring and all other things you can get from smart IDE in java for example.

Cedric Beust replied on Fri, 2010/04/16 - 11:35am

I agree with Artur, although I think that the main weakness of dynamically typed languages is not compilation but code readability. Source files without types are much harder to read, understand and modify than statically typed ones.

findSalary(employee)
return // request to find the employee and return their salary

Simple and easy to read, but hard to modify.

What is `employee`? The employee object? The unique id? Their last name?

Arnon Moscona replied on Fri, 2010/04/16 - 11:40am

I agree with Arthur B. above. The main productivity gain is in the IDE being "smarter" in a statically typed environment, as in a dynamic language it simply cannot possibly resolve everything without actually running the code.

This  productivity gain is offset by the need to write more boilerplate code in the statically typed languages than in dynamic languages. It's not that huge as much of the boilerplate code can be auto generated by most IDEs at a press of a button (or two).

Where the good IDEs really shine is refactoring. For instance, with IDEA I can rearrange code, move classes around, and do some rather sophisticated refactorings in Java that it cannot come even close to in either Groovy or Ruby.

So that's about productivity. About testing - I think the point is really moot. I've been practicing BDD in both dynamic and statically types languages for a while. I find that the amount of testing code needed for production quality code is about the same. This is not surprising as what matters is not so much code coverage (regardless of the particular metric used for it) but use case coverage. Unless you cover a relevant use case you can get surprised by a bug. It doesn't relly matter whether the bug is a typo that the IDE didn't catch or because your logic is broken. You're exposed just the same. From the testing and correctness perspective it doesn't matter whether you use the world's most brilliant IDE or vi.You must cover the relevant use cases, and ideally your code should not have much in it that does not directly handle the relevant use cases (e.g. a method that "seemed like it should be useful" but you didn't really need to make a test pass).

So bottom line: test first development is what gets you quality (BDD or otherwise). Dynamic or static language is largely irrelevant to this point. IDEs are for productivity and do somewhat better for static languages than for dynamic ones. Niether is a reason to choose one over the other.

If you want performance that you cannot possibly achieve with your dynamic language of choice - you have to use a statically typed languages. If you want lots of flexibility, write DSLs or can really leverage one that exists - dynamic languages are your best friend.

Rajiv Narula replied on Fri, 2010/04/16 - 1:32pm

It does ! I am not arguing that you shouldn't have test cases- but in a Dynamic Language like Groovy- you loose the safety net of the Compiler completely I like my compiler to check some things for me and for the more important (fragile? prone to changes ?)- I like to write Unit Test cases. But now in Groovy, I have lost the entire safety net that Compiler offers. And have to worry about every itsy bitsy thing myself

Adrian P. replied on Sat, 2010/04/17 - 10:08pm

Question is does Groovy code require more tests than Java code?

 Answer in the article is "write tests for your code."  Translation:  yes, it requires 100% test coverage.

 When I write in Scala or Java, I don't write test cases for trivial methods and I don't think that makes me a bad developer.  It saves time and complexity and reduces the development and maintenance burden.  In Groovy though, even simple assignments can cause runtime errors.

 To make matters worse. one of the places that Groovy has an apparent advantage is in GUI development - Griffon and the SwingBuilder allow for some very simple and clear code, but while it's possible to write test cases for Swing, it isn't easy and you can quickly spend more time writing and debugging your test cases than your actual code.  You're left with compile, run, crash, fix spelling error, compile, run, crash, fix spelling error, compile, run, crash, fix spelling error...  To make things even worse, even IntelliJ doesn't have good code assist during design time so making simple mistakes is all-too-easy.  I suspect that even though Scala is slightly more verbose, I'm more efficient overall.

I think Groovy has a place in a developer's arsenel but let's be honest and admit that it does have failings instead of attacking the messengers.

Venkateswara Ra... replied on Sun, 2010/04/18 - 6:38am

I accept that that one should have 100% coveraged unit test cases.

Fail case need extra fix time.

So if you have code that not allow you to write some bugs then you saved that all failed test cases.

 Concluding: It is always better to avoid issues sooner than laer.

Comment viewing options

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