Enterprise Java

JUnit, Logback, Maven with Spring 3

In this series we have already learnt to set up a basic Spring MVC application and learnt how to handle forms in Spring MVC. Now it is time to take on some more involved topics. However, before we venture into deeper waters, let’s get some basics set up.

Unit testing

I am no TDD evangelist. There I said it. I have never ever been able to write any software where for every piece of code, I have written a test first and then code. If you have done so and are gainfully employed by coding, please do let me know. I would seriously like to know you better. Seriously.

My difference in opinion with TDD ends there. Apart from writing test before code – which somehow I simply can’t get my brain to work with – I am a huge supporter of unit testing. I am a firm believer of using JUnit to test all functionality (public but non getter setter, methods). I am a huge fan of using cobertura to report on code coverage. I am a huge fan of maven which allows me to bring this all together in a nice HTML report with just one command.

I will use JUnit 4 for this series. Let’s add the dependencies.

File: \pom.xml

<properties>                                                     
    <junit.version>4.10</junit.version>
</properties>  

<!-- Unit testing framework. -->       
<dependency>                           
    <groupId>junit</groupId>           
    <artifactId>junit</artifactId>     
    <version>${junit.version}</version>
    <scope>test</scope>                
</dependency>                          

And let’s add a dumb class to demonstrate testing.

File: /src/main/java/org/academy/HelloWorld.java

package org.academy;

public class HelloWorld {
  private String message = 'Hello world. Default setting.'; 
  public String greet(){
    return message; 
  }
  
  public String getMessage() {
    return message;
  }
  public void setMessage(String message) {
    this.message = message;
  }
}

And finally the JUnit to test it.

File: src/test/java/org/academy/HelloWorldTest.java

package org.academy;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class HelloWorldTest {

  @Autowired
  HelloWorld helloWorld;
  
  private final static Logger logger = LoggerFactory
      .getLogger(HelloWorldTest.class);

  @Test
  public void test() {    
    logger.debug(helloWorld.greet());
    assertEquals(helloWorld.greet(), 'Hello world, from Spring.');
  }
}

You would have noticed that the helloWorld within the unit test have never been initialized in the code. This is the bit of IoC magic of Spring. To make this work, we have used @RunWith, @ContextConfiguration and @Autowired. And I have also given Spring enough information to be able to create an instance of HelloWorld and then inject it to HelloWorldTest.helloWorld. Also, the assertEquals is checking for a very different message than what is actually hard coded in the HelloWorld class. This was done in a xml file mentioned below. Please do note the location of the file within Maven structure.

File: /src/test/resources/org/academy/HelloWorldTest-context.xml

<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns='http://www.springframework.org/schema/beans'
  xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:p='http://www.springframework.org/schema/p'
  xmlns:context='http://www.springframework.org/schema/context'
  xsi:schemaLocation='http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-3.0.xsd'>

  <bean id='helloWorld' class='org.academy.HelloWorld'>
    <property name='message' value='Hello world, from Spring.' />
  </bean>
</beans>

There are multiple ways I could have provided this configuration file to the unit test. @RunWith(SpringJUnit4ClassRunner.class) is a nice thing to add but is not mandatory. What I have provided here is just the vanilla approach that works in most cases, but I encourage the audience to experiment.

Unit test coverage / code coverage.

I don’t feel there is enough said about the importance of automated / semi automated / easy way of reporting on code coverage – both for individual developers and technical heads. Unless you are practising TDD religiously (which by the way I have mentioned before I personally have never been able to), it is absolutely impossible for even an individual developer to know if all logic branches of a code are covered by unit test. I am not even going to talk about how a technical head of a team / organization is going to ensure that his product(s) are sufficiently unit tested. I personally believe, any software product which is not sufficiently unit tested and test coverage reported, is an unacceptable risk. Period. Admittedly a bit of a hard stance, but that’s how it is.

A bit of my conviction for the hard stance comes from the fact that it is so darn easy to report on test coverage. I will use cobertura in this example. You need to add cobertua to Maven pom.

File: pom.xml

<!-- Reporting -->                                              
<plugin>                                                              
  <groupId>org.apache.maven.plugins</groupId>                       
  <artifactId>maven-site-plugin</artifactId>                        
  <version>3.0</version>                                            
  <configuration>                                                   
    <reportPlugins>                                               
      <!-- Reporting on success / failure of unit tests -->     
      <plugin>                                                  
        <groupId>org.apache.maven.plugins</groupId>           
        <artifactId>maven-surefire-report-plugin</artifactId> 
        <version>2.6</version>                                
      </plugin>                                                 
      <!-- Reporting on code coverage by unit tests. -->        
      <plugin>                                                  
        <groupId>org.codehaus.mojo</groupId>                  
        <artifactId>cobertura-maven-plugin</artifactId>       
        <version>2.5.1</version>                              
        <configuration>                                       
          <formats>                                         
            <format>xml</format>                          
            <format>html</format>                         
          </formats>                                        
        </configuration>                                      
      </plugin>                                                 
    </reportPlugins>                                              
  </configuration>                                                  

And once you have done this, and added JUnit, and added an actual JUnit test, you just need to run

mvn -e clean install site

to create a nice looking HTML based code coverage report. This report will allow you to click through source code under test and give you nice green coloured patches for unit tested code and red coloured patches for those that slipped through the cracks.

Logging

Log4j is good, Logback is better. Just don’t use System.out.println() for logging.

You could go a long way without proper logging. However, I have spent far too many weekends and nights chasing down production issues, with business breathing down my neck, wishing there was some way to know what was happening in the app rather than having to guess all my way. Now a days, with mature api like slf4j and stable implementation like logback, a developer needs to add just one extra line per class to take advantage of enterprise grade logging infrastructure. It just does not make sense not to use proper logging right from the beginning of any project.

Add slf4j and logback to Maven dependencies.

File: \pom.xml.

<!-- Logging -->                            
<dependency>                                
  <groupId>ch.qos.logback</groupId>       
  <artifactId>logback-classic</artifactId>
  <version>${logback.version}</version>   
</dependency>                               

Ensure that Spring’s default logging i.e. commons logging is excluded. If you are wondering if logback is really this good that I claim it to be why did Spring not opt for it to start with. In my defense, here is a link at Spring’s official blog where they say ‘If we could turn back the clock and start Spring now as a new project it would use a different logging dependency. Probably the first choice would be the Simple Logging Facade for Java (SLF4J),…’

File: \pom.xml.

<!-- Support for testing Spring applications with too
  TestNG This artifact is generally always defined 
  the integration testing framework and unit testin
<dependency>                                         
  <groupId>org.springframework</groupId>           
  <artifactId>spring-test</artifactId>             
  <version>${org.springframework.version}</version>
  <scope>test</scope>                              
  <exclusions>                                     
    <exclusion>                                  
      <groupId>commons-logging</groupId>       
      <artifactId>commons-logging</artifactId> 
    </exclusion>                                 
  </exclusions>                                    
</dependency>                                        

Provide configuration for logback.

File: /src/main/resources/logback.xml 

<?xml version='1.0' encoding='UTF-8'?>
<configuration>
  <appender name='CONSOLE' class='ch.qos.logback.core.ConsoleAppender'>
    <encoder>
      <pattern>%d %5p | %t | %-55logger{55} | %m %n</pattern>
    </encoder>
  </appender>

  <logger name='org.springframework'>
    <level value='INFO' />
  </logger>

  <root>
    <level value='DEBUG' />
    <appender-ref ref='CONSOLE' />
  </root>
</configuration>                                 

Finally, add the magic one liner at the beginning of each class that needs logging (that ought to be all classes).

File: src/test/java/org/academy/HelloWorldTest.java

[...]                                                    
private final static Logger logger = LoggerFactory  
  .getLogger(HelloWorldTest.class);           
[...]
logger.debug(helloWorld.greet());
[...]

There you are all set up. Now is the time to wade deeper into Spring.

Happy coding.

Want to read more?

Here are the links to earlier articles in this series.
Hello World with Spring 3 MVC
Handling Forms with Spring 3 MVC

And, of course these are highly recommended
Spring 3 Testing with JUnit 4.
Running unit tests with the Spring Framework
@RunWith JUnit4 with BOTH SpringJUnit4ClassRunner and Parameterized
Issue with Junit and Spring.

Reference: JUnit, Logback, Maven with Spring 3 from our JCG partner Partho at the Tech for Enterprise blog.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
-
-
12 years ago

It’s generally considered good practice not to use Spring in unit tests where possible.

> it is absolutely impossible for even an individual developer to know if all logic branches of a code are covered by unit test.

Untrue. When run from Ant, Corbertura and Clover can be easily configured to fail if specific code coverage (including branches) is not met and to produce a report of which lines/branches are missed. On any project I lead, the build fails if code coverage drops below 100% and my team have never encountered any problems with that.

Eric Jablow
12 years ago

Writing tests for getters and setters will be useful if your class enforces invariants. If you have a DateRange class that enforces start ≤ end, you should test that a setStart or setEnd call does not break that. And, since Dates are mutable, you should have a test that checks for aliasing: if you set the start variable and get it again, you should get a distinct instance.

Back to top button