JUnit, Logback, Maven with Spring 3
Join the DZone community and get the full member experience.
Join For FreeIn 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.
Published at DZone with permission of Partha Bhattacharjee, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Generics in Java and Their Implementation
-
Azure Virtual Machines
-
How to LINQ Between Java and SQL With JPAStreamer
-
How To Use an Automatic Sequence Diagram Generator
Comments