Tuesday, December 13, 2011

JUnit 4.10 by example

Junit is surely the most popular testing framework for the java platform and it is a must have in your software development toolkit. That’s good reasons to adopt it right now or to simply refresh your knowledge and to read this article :)
The goal of this tutorial is to introduce JUnit and to show how it is simple to systematically use it through a real-life example: an application which stores and retrieves its data to and from a database.

1. Prerequisites

To build the project you will need a Java Development Kit in version 6 and a maven in version 2.2.1 installed and configured on your machine. Please create a new java project using the maven standard layout. We are going to build a J2SE application so create at least the following directories:
src/main/java/ for sources
src/main/resources/ for resources
src/test/java/ for test sources
Then create the following pom.xml file in the root folder:
<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>myproject</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>myproject</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.11</version>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.derby</groupId>
      <artifactId>derby</artifactId>
      <version>10.8.1.2</version>    
    </dependency>        
    <dependency> 
      <groupId>org.hibernate</groupId> 
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.6.8.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>3.6.8.Final</version>
    </dependency>
  </dependencies>
</project>
As you can see, we are going to use Hibernate as persistence framework and Apache Derby as database. Now type make install to download all the dependencies.

2. Create the model classes

We are going to develop a simple application that uses a database to store and retrieve its data. Our database contains two tables. A PRODUCT table used to store individual information about a product. And a COMPANY table used to store information about a company. Products manufactured by a company are represented through an association between the two tables.
As we are using an Object Relational Mapping tool like Hibernate, we just have to describe our model in Java. The database schema will be automatically generated.
So with JPA (Java Persistence API), our object model looks as follows:
package com.example.myproject.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Product
{
    @Id
    @GeneratedValue
    private Integer id;
    
    private String name;

    public Integer getId()
    {
        return id;
    }

    public void setId(final Integer id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(final String name)
    {
        this.name = name;
    }
}

package com.example.myproject.model;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Company
{
    @Id
    @GeneratedValue
    private Integer id;
    
    private String name;
    
    @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    private List<Product> products;
    
    public Integer getId()
    {
        return id;
    }

    public void setId(final Integer id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(final String name)
    {
        this.name = name;
    }

    public List<Product> getProducts()
    {
        return products;
    }

    public void setProducts(final List<Product> products)
    {
        this.products = products;
    }
}
Both entities have a primary key "id" automatically generated by the persistence layer and a "name" field set by the user.

3. First implementation

To design our application we decide to apply the Data Access Object pattern. All CRUD (Create, Read, Update, Delete) operations will be delegated to a CompanyDAO class and a ProductDAO class implementing a common DAO interface:
package com.example.myproject.dao;

import java.io.Serializable;
import java.util.List;

public interface DAO<Entity extends Object, ID extends Serializable>
{
    public Entity get(ID id);
   
    public List<Entity> listAll();
   
    public void remove(Entity e);
   
    public void save(Entity e);
   
    public void update(Entity e);
}
To factorize these operations, we create a generic DAO:
package com.example.myproject.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.persistence.EntityManager;

import org.hibernate.Criteria;
import org.hibernate.Session;

import com.example.myproject.dao.DAO;

public abstract class AbstractDAO<Entity extends Object, ID extends Serializable> implements DAO<Entity, ID>
{
    protected EntityManager em;
    
    private final Class<Entity> persistentClass;
    
    @SuppressWarnings("unchecked")
    public AbstractDAO()
    {
        persistentClass = 
            (Class<Entity>)((ParameterizedType)getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0];
    }
    
    public void setEntityManager(final EntityManager em)
    {
        this.em = em;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public List<Entity> listAll()
    {
        final Session session = (Session)em.getDelegate();
        final Criteria crit = session.createCriteria(persistentClass);
        
        return crit.list();
    }

    public Entity get(final Integer id)
    {
        return em.find(persistentClass, id);
    }

    @Override
    public void remove(final Entity e)
    {
        em.remove(e);        
    }

    public void save(final Entity e)
    {
        em.persist(e);
    }

    public void update(final Entity e)
    {
        em.merge(e);        
    }
}
Our first DAO implementations are quite simple:
package com.example.myproject.dao.hibernate;

import com.example.myproject.model.Product;

public class ProductDAO extends AbstractDAO<Product, Integer>
{

}

package com.example.myproject.dao.hibernate;

import com.example.myproject.model.Company;

public class CompanyDAO extends AbstractDAO<Company, Integer>
{

}
We are almost ready to run our first test. Let’s add the following persistence.xml file in the src/main/resources/META-INF folder of the project:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="myprojet" transaction-type="RESOURCE_LOCAL">
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>
         <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver"/>
         <property name="hibernate.connection.autocommit" value="true"/>
         <property name="hibernate.connection.release_mode" value="after_transaction"/>
         <property name="hibernate.connection.url" value="jdbc:derby:mydb;create=true"/>
         <property name="hibernate.hbm2ddl.auto" value="create"/>
      </properties>
   </persistence-unit>
</persistence>
Hibernate is configured to use the Apache Derby JDBC driver and to automatically export the generated schema to the database.

4. Create the JUnit tests

Good pratice is to create the JUnit test classes in a different location than the sources files. By convention, they are added to the src/test/java directory. Another tip is to create the test class in the same package than the tested class in order to be able to test private package methods.
package com.example.myproject.dao.hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.example.myproject.model.Company;

public class CompanyDAOTest
{
    private static EntityManagerFactory emf;
   
    private EntityManager em;
    private CompanyDAO dao;
   
    @BeforeClass
    public static void initDatabase()
    {
        emf = Persistence.createEntityManagerFactory("myprojet");
    }

    @AfterClass
    public static void closeDatabase()
    {
        emf.close();
    }
   
    @Before
    public void initTest()
    {
        em = emf.createEntityManager();
        dao = new CompanyDAO();
        dao.setEntityManager(em);
    }
   
    @After
    public void afterTest()
    {
        em.close();
    } 
   
    @Test
    public void testSaveCompany()
    {
        final EntityTransaction tx = em.getTransaction();
        tx.begin();
       
        final Company company = new Company();
        company.setName("Global company");
        dao.save(company);
       
        tx.commit();
       
        Assert.assertNotNull("Company id not set", company.getId());
    }   
   
    @Ignore("TODO")
    @Test
    public void testDeleteCompany(){ }
   
    @Ignore("TODO")
    @Test
    public void testGetCompany() { }
   
    @Ignore("TODO")
    @Test
    public void testListAllCompany() { }
   
    @Ignore("TODO")
    @Test
    public void testUpdateCompany() { }
}
A method annoted with @Test tells Junit that the method can be run as a test case. If an exception is thrown by this method, a failure will be reported by JUnit. Otherwise JUnit will consider it as successful.
If you want to temporarily disable one of your test, use the @Ignore annotation rather than comment the Test annotation. The test will not be executed by JUnit but your test runner will clearly report the number of ignored tests. It is the best way to not forget that there is still some work to do.
Methods annoted with @Test should use assertion methods from org.junit.Assert class. If an assertion is not verified it will throw an AssertionError and the test will fail. In our example we check that the persistence layer has correctly generated an identifier for our record.
Note that the first parameter of the assertNotNullMethod is optional but prefer to use it as it will be used to construct the AssertionError and whenever a test will be broken, you will know immediately why.
A method annoted with @BeforeClass is run once before any method in the test class. It is used for expensive setup, here database connection and object relational mapping layer initialization.
@AfterClass annotation is used to release resources allocated in the method annotated with @BeforeClass.
A method annotated with @Before is run before each test method and a method annotated with @After is executed right after each test method. It is useful to create a clean context before each test. Remember that each test method should be independent and may be run in any order.

5. Run the JUnit tests with maven

The command mvn test –Dtest=CompanyDAOTest will execute all annotated methods in the CompanyDAO class and should produce the following output:
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.example.myproject.dao.hibernate.CompanyDAOTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 4, Time elapsed: 2.053 sec
Results :
Tests run: 5, Failures: 0, Errors: 0, Skipped: 4
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
It is also possible to launch a single test method:
mvn test –Dtest=CompanyDAOTest#testSaveCompany
Optional annotated methods with beforeClass, before, after, afterClass will of course be executed if present.
Next step is to create a ProductDAOTest class. Use CompanyDAOTest class as model. Both classes should be very similar. Once done use the following command to run all of your tests:
mvn test

6. Write tests first

Ideally we should write an automated test before writing any code. This way we are focused on the interface and not on the implementation. Then and only then we can write the implementation. Since there is already a test the new method can be easily debugged and corrected.
For example, a company in our application needs to register its products. So we have to add a registerProduct method in the CompanyDAO class:
    @Test
    public void testRegisterProduct()
    {
        EntityTransaction tx = em.getTransaction();
        tx.begin();        
        final Company company = new Company(); 
        company.setName("Global company");
        dao.save(company);        
        tx.commit();
        
        tx = em.getTransaction();
        tx.begin();
        final Product product = new Product();
        product.setName("Washing machine");
        dao.registerProduct(company, product);
        final int nbProducts = company.getProducts().size();
        tx.commit();
        
        Assert.assertEquals("Product registering failed", 
            1, nbProducts);
    }
The design of the registerProduct method seems ok. We can then start the implementation.

7. Test error cases

Thinking about interface also means to take into account all possible errors. For example after the registerProduct we want to add an unregisterMethod. If the product passed to the method is not already registered, it should throw an exception:
    @Test(expected=UnknownProductException.class)
    public void testUnregisterProduct() throws UnknownProductException
    {
        EntityTransaction tx = em.getTransaction();
        tx.begin();        
        final Company company = new Company(); 
        company.setName("Global company");
        dao.save(company);        
        tx.commit();
        
        tx = em.getTransaction();
        tx.begin();
        final Product product = new Product();
        product.setName("Washing machine");
        dao.unregisterProduct(company, product);
        tx.commit();
    }
The test annotation supports an optional parameter named “expected”. If no exception is thrown or if the exception thrown is not of type UnknownProductException, the test will fail.

8. JUnit test suite

In a real-life application we will surely create much more DAO and DAOTest classes. And performance may suffer because for each test case we create an EntityManagerFactory at initialization which is expensive. To address this problem it is possible to gather tests that share the same initialization needs in one test suite class. Because all of our DAOTest classes need an EntityManagerFactory, we will use ClassRule to initialize the factory before executing the test suite and to close it right after its execution.
package com.example.myproject.dao.hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.junit.ClassRule;
import org.junit.rules.ExternalResource;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RunWith(Suite.class)
@SuiteClasses({CompanyDAOTest.class, ProductDAOTest.class})
public class DAOTestSuite
{
    private static EntityManagerFactory emf;
   
    @ClassRule
    public static ExternalResource resource = new ExternalResource()
    {
        @Override
        protected void after()
        {
            emf.close();
        }

        @Override
        protected void before() throws Throwable
        {
            emf = Persistence.createEntityManagerFactory("myprojet");
        }       
    };
   
    public static EntityManager createEntityManager()
    {
        return emf.createEntityManager();
    }
}
Next step is to remove initDatabase and closeDatabase methods from all our DAO classes:
package com.example.myproject.dao.hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import com.example.myproject.model.Company;

public class CompanyDAOTest
{
    private EntityManager em;
    private CompanyDAO dao;
   
    @Before
    public void initTest()
    {
        em = DAOTestSuite.createEntityManager();
        dao = new CompanyDAO();
        dao.setEntityManager(em);
    }
   
    @After
    public void afterTest()
    {
        em.close();
    } 
   
    @Test
    public void testSaveCompany()
    {
        final EntityTransaction tx = em.getTransaction();
        tx.begin();
       
        final Company company = new Company();
        company.setName("Global company");
        dao.save(company);
       
        tx.commit();
       
        Assert.assertNotNull("Company id not set", company.getId());
    }   
   
    @Ignore("TODO")
    @Test
    public void testDeleteCompany(){ }
   
    @Ignore("TODO")
    @Test
    public void testGetCompany() { }
   
    @Ignore("TODO")
    @Test
    public void testListAllCompany() { }
   
    @Ignore("TODO")
    @Test
    public void testUpdateCompany() { }
}
And to slightly modify our pom.xml in order to execute the test suite:
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.11</version>
        <configuration>
          <includes>
            <include>**/*TestSuite.java</include>
          </includes>
        </configuration>
      </plugin>
Launching the mvn test command will now run our test suites and the EntityManagerFactory will be initialized only one time.

9. External links

http://maven.apache.org/
The maven website.
http://maven.apache.org/plugins/maven-surefire-plugin/
Maven plugin used during the test phase of the build lifecycle.


No comments:

Post a Comment