Short on Time? Switch to Groovy for Unit Testing
If you are a programming today, you have most likely heard of Unit Testing or of the Test-Driven Development process. I have yet to run into a programmer that has not heard of both, nor one that says Unit Testing isn’t important. In casual discussions, most programmers seem to feel that Unit Tests are very important and that Test-Driven Development (TDD) is a worthy process to emulate.
But I get a completely different story when I start working with the code and ask, “where are the Unit Tests?” I recently asked my programmer friends online why they don’t, and, as a follow-on, why other programmers don’t write Unit Tests. The number one answer was one I that often hear when I ask programmers or IT managers the same question: “I don’t have time,” or something to that effect. It’s often followed by the argument that it takes 20% longer to write an application with Unit Tests than without, and “we’re under time constraints.”
Other responses that I received from my impromptu online survey that interested me were, “inertia,” “not knowing how,” and “Tests are not fun. Programming is fun. We would rather program.” (For full disclosure, the “not knowing how” answer was to the question, “Why don’t other programmers write Unit Tests?”)
Inertia I understand. If you are new to a shop, and there are 4 unit tests to 2000 objects, you probably won’t buck the trend and be the one guy that writes Unit Tests. Not knowing how is something every programmer will have to overcome.
My suggestion – as we try to help solve the lack of time problem, maybe we can do something about the fun deficit.
In Practice
I’m working on a prototype for an application that will allow users to enter in information about a home improvement project, and then share the material and tool information for that project with their friends. The friends can then promise to loan or purchase some of the material or a tool that is needed in the project. Basically a “Bridal Registry” for home improvement projects.
The test will be on a method that will take a Project object, iterate through the tools list for that project to see if that tool has been promised already, and create a list of tools that are not promised. It will then pass that list to a service that will look up the current price of each tool.
The prototype is done with Grails, but we will write this method in Java:
public List<Tool> neededToolList(Project project) { final List<Tool> retList = new ArrayList<>(); if (project.getTools() == null || project.getTools().isEmpty()) { return retList; } for (Tool tool : project.getTools()) { if (!tool.getPromise().isPromised()) { retList.add(tool); } } List<Tool> tools = lookupService.updateToolList(retList); return tools; }
A single Unit Test could look something like:
@Test public void testNeededToolList() { Tools _instance = new Tools(); Project project = new Project(); Promise promise = new Promise(); promise.setProject(project); promise.setPromised(false); Promise promise2 = new Promise(); promise2.setProject(project); promise2.setPromised(true); List<Tool> tools = new ArrayList<>(); List<Tool> lookupTools = new ArrayList<>(); Tool tool1 = new Tool(); tool1.setName("table saw"); tool1.setStoreId("T001"); tool1.setPromise(promise); tools.add(tool1); lookupTools.add(tool1); Tool tool2 = new Tool(); tool2.setName("pneumatic nail guns"); tool2.setStoreId("T027"); tool2.setPromise(promise2); tools.add(tool2); project.setTools(tools); List<Tool> mockedTools = new ArrayList<>(); Tool mockedTool1 = new Tool(); mockedTool1.setPromise(promise); mockedTool1.setName("table saw"); mockedTool1.setStoreId("T001"); mockedTool1.setPrice(129.0); mockedTools.add(mockedTool1); lookupService = Mockito.mock(LookupServiceImpl.class); Mockito.when(lookupService.updateToolList(lookupTools)).thenReturn(mockedTools); _instance.setLookupService(lookupService); List<Tool> returnedTools = _instance.neededToolList(project); assertTrue(returnedTools.size() == 1); for(Tool tool : returnedTools) { assertEquals(129.0, tool.getPrice(), 0.01); } }
This is a simple test, and only one. There needs to be tests written for several situations, such as null values. What if the StoreID doesn’t exist for instance?
Enter Groovy
In a previous article, Don’t Fear the Rapid, I attempted to introduce the readers to my good friend, the Groovy Programming Language. Let’s see if we can make testing Groovy.
Groovy brings a lot of shortcuts in syntax that help to speed up writing code, including tests. Let’s take a look at a possible way to rewrite that test in Groovy.
class GroovyToolsTest extends GroovyTestCase { def lookupService = [ updateToolList : {List<Tool> toolList -> toolList.each { if(it.storeId == "T001") { it.price = 129.0 } } return toolList } ] as LookupService void testNeededToolList() { def _instance = new Tools() def project = new Project() project.tools = [ new Tool(name: "table saw", storeId: "T001", promise: new Promise(project: project, promised: false)), new Tool(name: "pneumatic nail guns", storeId: "T027", promise: new Promise(project: project, promised: true)) ] _instance.lookupService = lookupService def returnedList = _instance.neededToolList(project) returnedList.size() == 1 returnedList.each { if(it.storeId == "T001") { assert it.price == 129.0 } } println "done" } }
The first thing we see is that Groovy gives us a great mechanism for Mocking code that allows us to do a lot more than I’ve been able to do in Mocking frameworks. In a mocking framework, I typically create a new object for the data I expect to return. Here I’m actually changing the data to what the service is supposed to return.
Remember: I’m not testing the service, so the Mocked service should return the value I expect the service to return.
I also find the ability to create objects and load the data in one call (as opposed to creating the bean, and calling each setter), to be easier to write, read, and copy as a template to create more. Groovy provides several methods for dealing with lists that make it a great language for quickly developing, and maintaining, tests.
If you care to think a little differently for your unit tests, there is also the Spock testing framework. It has a more expansive language that gives it a more behavior-driven look and feel, but it still uses all the Groovy Goodness from the previous example.
class ToolsSpec extends Specification { def lookupService = [ updateToolList : {List<Tool> toolList -> println "mocked service" toolList.each { tool -> if(tool.storeId == "T001") tool.price = 129.0 } return toolList } ] as LookupService def "Lookup needed tool list"() { given:"Create instance" def _instance = new Tools() def project = new Project() project.tools = [ [name: "table saw", storeId: "T001", promise: [project: project, promised: false] as Promise] as Tool, [name: "pneumatic nail guns", storeId: "T027", promise: [project: project, promised: true] as Promise] as Tool, ] as List<Tool>; _instance.lookupService = lookupService expect:"Tool List" def returnedList = _instance.neededToolList(project) returnedList.size() == 1 returnedList.each { if(it.storeId == "T001") { assert it.price == 129.0 } } } }
Notice that I used a different syntax to create the test data objects for Tool. This is a standard Groovy feature that allows the programmer to convert a map to a concrete class and could have also been used in the previous example. As you get used to reading Groovy, this might be easier to read than the new Object syntax.
In both examples, tighter code with syntax “sugar” is not the only benefit. The output from a failed test is also different and is far more helpful
The output of a failed test in the first example is:
java.lang.AssertionError: expected:<128.0> but was:<129.0> at org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834) at org.junit.Assert.assertEquals(Assert.java:553) at org.junit.Assert.assertEquals(Assert.java:683) at org.projectregistry.services.ToolsTest.testNeededToolList(ToolsTest.java:93) ....
The output from the Groovy and Spock tests look like:
Assertion failed: assert it.price == 128.0 | | | | 129.0 false org.projectregistry.model.Tool@5e59238b at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:399) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:648) at org.projectregistry.services.GroovyToolsTest$_testNeededToolList_closure2.doCall(GroovyToolsTest.groovy:34) ...
There is a lot more information given in the Groovy output, which in turn gives you a possibility of a faster fix.
So with the time that could save with the improved syntax and output, and hopefully a new and different language to add to the programming fun, I’m hoping that everyone will give Groovy and/or Spock a try and overcome the inertia that prevents programmers from doing Unit Tests.
Learning how is simple. Both Groovy and Spock are well documented and there are many resources just a Google* search away. There is also a very lively and helpful community on various social media that I’m sure would love to help.
Reference: | Short on Time? Switch to Groovy for Unit Testing from our JCG partner Rik Scarborough at the Keyhole Software blog. |
I really see no point in testing grails code using Java!
And since I found out how much time I save by writing unit tests in groovy (even for a java code base) I think I will try to use groovy test in every project. I simply see no real drawback in that.
The problem with Java code you presented, is not Java’s fault. It is that this is an awful test which looks ugly in any language. Seriously. There is this book about writing good tests “Bad Tests, Good Tests”, please have a look: http://practicalunittesting.com/btgt.php Cheers!
IMHO It would be even better if you would show an example with “where” block.
Regarding unreadable code, even Spock won’t help if someone got not clue what Refactoring is about. ;-)