Friday, May 31, 2013

Unit Test and Integration Test Fun with Maven / Spring / Java


Time and time again, I have either worked on, witnessed, been an advisor to, projects and how applications break when developers check in bad code!  Breaking the app could mean multiple things:

  • doesn't compile
  • doesn't run, perhaps a spring bean error prevents an application from starting
  • fixed one thing but broke five others

My boss and colleagues affectionately call me the "Code Janitor" at work because I am an avid advocator of adequate unit and integration tests, and automated deployments to a development environment on each code check-in, or some other time-defined interval.  If these things existed in all projects, then a majority of app breakage could be caught and prevented.  Here are good links to give you background on unit tests (with or without mock objects) and integration tests with java:



I will discuss a very simple example of unit and integration tests for a maven spring java project to get you started!

CherryShoe class will act as our "main" class, Cherry will have a color attribute, and Shoe will have a type attribute.

CherryShoe class
package com.cherryshoe.example;

import com.cherryshoe.model.Cherry;
import com.cherryshoe.model.Shoe;

public class CherryShoe {
    private Cherry cherry;
    private Shoe shoe;

    public Cherry getCherry() {
        return cherry;
    }

    public void setCherry(Cherry cherry) {
        this.cherry = cherry;
    }

    public Shoe getShoe() {
        return shoe;
    }

    public void setShoe(Shoe shoe) {
        this.shoe = shoe;
    }

    public boolean isRed() {
        if (getCherry().getColor().equals("Red")) return true;
        return false;
    }
    
    public boolean isType() {
        if (getShoe().getType().equals("Hiking")) return true;
        return false;
    }
}


Cherry class
package com.cherryshoe.model;

public class Cherry {
    private String color;
    
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
}


Shoe class
package com.cherryshoe.model;

public class Shoe {
    private String type;
    
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
}

Project maven pom
The maven pom file for the project will need to grab dependencies for easymock and junit.
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <scope>test</scope>
        </dependency>


Here's the sample spring spring-config-cherryshoe.xml file, injecting the cherry color, and shoe type.
<?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:jee="http://www.springframework.org/schema/jee"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
          http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
">
        
    <bean id="my.cherryshoe" class="com.cherryshoe.example.CherryShoe">
        <property name="cherry" ref="my.cherry"/>
        <property name="shoe" ref="my.shoe"/>
    </bean>
    
    <bean id="my.cherry" class="com.cherryshoe.model.Cherry">
        <property name="color" value="Red"/>
    </bean>
    
    <bean id="my.shoe" class="com.cherryshoe.model.Shoe">
        <property name="type" value="Hiking"/>
    </bean>

</beans>



Unit Tests with Mocks
A unit test with mock objects tests the contract between yourself (the class) and your collaborators.  The general rule I advocate is you don't have to mock classes that you are using from a 3rd party library since you hope those have gone through vigorous unit and integration testing on their own! (i.e. Oracle Sun API classes, apache classes, etc.), you are testing your own custom classes.

Here's the sample CherryShoeUnitTest unit test using mock Cherry and Shoe objects.  It should live in the same project as where your code is, it should be fast and run routinely as you develop.

CherryShoeUnitTest class
package com.cherryshoe.example;

import org.junit.Before;
import org.junit.Test;

import static org.easymock.EasyMock.expect;
import org.easymock.EasyMockSupport;

import com.cherryshoe.model.Cherry;
import com.cherryshoe.model.Shoe;

import static org.junit.Assert.assertEquals;

// EasyMockSupport lets you replayAll and verifyAll, great library!
public class CherryShoeUnitTest extends EasyMockSupport {
    private Cherry mockCherry;
    private Shoe mockShoe;
    
    private CherryShoe unit;

    @Before
    public void setUp() throws Exception {
        // set up your mocks
        mockCherry = createMock(Cherry.class);
        mockShoe = createMock(Shoe.class);
        
        // set up your unit that is being tested
        unit = new CherryShoe();
        unit.setCherry(mockCherry);
        unit.setShoe(mockShoe);
    }

    @Test
    public void testGetCherryColor_good() {
        // set up values needed for unit test
        String color = "Red";

        // set up expectations
        expect(mockCherry.getColor()).andReturn(color);

        // record expectations
        replayAll();

        // exercise code under test
        Boolean isRed = unit.isRed();
        
        // verify expectations
        verifyAll();

        // this should be true since Red is what I expected it to be in expectations above
        assertEquals(isRed, true);
    }
    
    @Test
    public void testGetCherryColor_bad() {
        // set up values needed for unit test
        String color = "Yellow";

        // set up expectations
        expect(mockCherry.getColor()).andReturn(color);

        // record expectations
        replayAll();

        // exercise code under test
        Boolean isRed = unit.isRed();
        
        // verify expectations
        verifyAll();

        // this should be false since Yellow is what I expected it to be in expectations above
        assertEquals(isRed, false);
    }   

    @Test
    public void testGetType_good() {
        // set up values needed for unit test
        String type = "Hiking";

        // set up expectations
        expect(mockShoe.getType()).andReturn(type);

        // record expectations
        replayAll();

        // exercise code under test
        Boolean isHiking = unit.isType();
        
        // verify expectations
        verifyAll();

        // this should be true since Hiking is what I expected it to be in expectations above
        assertEquals(isHiking, true);
    }
    
    @Test
    public void testGetType_bad() {
        // set up values needed for unit test
        String type = "Running";

        // set up expectations
        expect(mockShoe.getType()).andReturn(type);

        // record expectations
        replayAll();

        // exercise code under test
        Boolean isHiking = unit.isType();
        
        // verify expectations
        verifyAll();

        // this should be true since Running is what I expected it to be in expectations above
        assertEquals(isHiking, false);
    }

    
}



Integration Tests
Integration tests verify that your code really works.  If my example had been a more realistic example (i.e. talking to a database), then the database would need to be running.  The integration test should live in its own project so it won't hold up a build if it fails (i.e. database is down); also it could take a long time to run.

You should even write Spring integration tests to test that your spring configurations are valid.  In the example here, I haven't since autowiring the beans, and using the spring contexts of spring files available in your classpath implicitly tests this for you (You didn't hear that from me!).


Integration Test Project maven pom
The maven pom file for the integration test project will need to grab dependencies for your custom project and junit.
        <dependency>            
            <groupId>com.cherryshoe.example</groupId>
            <artifactId>cherryshoe-example</artifactId>
            <version>${com.cherryshoe.example.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>



Here's the sample CherryShoeTest integration test:
CherryShoeTest class
package com.cherryshoe.example;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

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.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.armedia.ecm.service.CmisEcmService;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =
{
    "/spring/spring-config-cherryshoe.xml"
})
public class CherryShoeTest {

        @Autowired
        @Qualifier("my.cherryshoe")
        private CherryShoe cherryShoe;
        
        @Test
        public void test_isRed() throws Exception
        {
            Boolean isRed = cherryShoe.isRed();
            
            // this should be true, since the spring config file was red
            assertEquals(isRed, true);
        }

        @Test
        public void test_isType() throws Exception
        {
            Boolean isHiking = cherryShoe.isType();
            
            // this should be true, since the spring config file was Hiking
            assertEquals(isHiking, true);
        }

}






1 comment:

I appreciate your time in leaving a comment!