Chapter 8. Effective Testing

Automated testing, and unit testing in particular, is now universally regarded as a best practice for software development. A number of testing frameworks are available, and there remains plenty of room to debate the merits of unit testing versus integration testing, whether to mock objects with interesting behavior with frameworks like jMock or EasyMock or take a more classic approach with basic stubs, when to apply test-driven development (TDD), whether behavior-driven development (BDD) will become commonplace, and so on.

Throughout this book, we've highlighted several best practices, including layering your application and coding to interfaces. In this chapter, we'll demonstrate how these principles lend themselves to building solid test coverage with proper emphasis on exercising aspects of an application in isolation.

A code base that is broken down into layers so that each layer has a unique responsibility is much more testable than code that attempts to combine multiple aspects of functionality into a single class. Testable code is code that is decoupled and divided into logical layers, and well-layered code is testable because it produces small, defined parts of an application's overall vision. By coding to interfaces and leveraging Spring's dependency-injection capabilities, you gain the ability to mock or stub one layer (such as the DAO layer) when you're testing the layer above it (in this case, the service layer that uses DAOs).

Dependency-injection frameworks like Spring are tremendously useful for testing because they make it relatively easy to instantiate classes directly, providing collaborators through code. With Spring in particular, you can automatically inject dependencies within your test classes simply by specifying the appropriate annotations. This allows you to construct an application context for your tests that uses configuration options that may differ quite a bit from your production setup. This flexibility enables you to test your code against a large number of potential inputs.

When it comes to verifying assertions of a persistence tier, it is important to verify the behavior of your DAO and service classes, the configuration details and behavior of your domain classes, and even the collaboration and wiring of dependencies.

We will skim the surface of these strategies in this chapter, but it is important to keep in mind that an effective testing strategy should incorporate both unit and integration tests. Luckily, Spring helps to simplify the creation of both of these kinds of tests, as well as other forms of automated testing, such as functional testing with tools like Selenium.

Unit, Integration, and Functional Testing

Spring makes it easy to test specific parts of your code without relying on an application server or other infrastructural details. You can switch between different database implementations and datasources, or test your DAO classes in isolation by mocking these details.

Unit testing is an effective strategy for verifying that a particular class works properly in isolation. Assessing classes in isolation is very valuable, and there is no commensurate replacement for a good unit test. Writing an effective unit test involves the definition of assertions regarding the behavior of specific areas of a class in isolation. Good test coverage is related to which lines of code have their expected behavior verified.

Unlike unit testing, integration testing typically verifies multiple components simultaneously, often by running the same implementation layers used within the production version of an application. For instance, a common practice for integration testing is to instantiate the Spring ApplicationContext and test a DAO implementation using a real database along with the Spring Hibernate abstractions. The advantage of this approach is that you are touching multiple components, ensuring that all the pieces are working together properly. The disadvantage is that it doesn't provide much granularity to ascertain whether a particular component works properly on its own. For a comprehensive testing strategy, we recommend including both integration and unit tests.

A test suite is a set of individual test classes that are designed to run together and that typically make assertions related to a particular layer or component. For example, you can create a DAO test suite composed of all of your DAO tests. The following example shows all you need to do to create a suite of tests:

public void static testSuite() {
    return new TestSuite(ArtworkDao.class,
                         CategoryDao.class,
                         ExhibitionDao.class,
                         PersonDao.class);
}

Modern IDEs (Eclipse, IntelliJ IDEA, NetBeans, and many more) and other runtime environments (such as Ant and Maven) know how to run both individual unit tests and test suites, which can include both unit and integration tests. It's common to use the notion of suites to strategically bundle tests together. For example, you might want a test suite of fast unit tests that are run on every commit and a different test suite composed of longer-running integration tests, which are done on some scheduled interval.

Functional tests are another strategy for verifying your application is behaving properly. Functional tests provide the most high-level assessment of a code base, and typically require that an application run within a production environment container—for instance, using a servlet container.

Functional tests in a web application context usually involve a series of HTTP requests and then assertions as to the responses that should be returned. For example, a REST web service might include a battery of functional tests that verify the data that is returned when a chunk of XML is POSTed to a particular URL. Another type of functional test might verify certain HTML elements within a returned HTTP response, given a particular URL.

The downside of functional tests, especially as they relate to verifying HTML markup, is that they tend to be very brittle—meaning they are likely to break as a result of minor changes to the application. However, functional tests do have their place, and they are often an effective tool to verify basic assumptions regarding an application's behavior.

Using JUnit for Effective Testing

The two biggest unit testing frameworks in the Java community at present are JUnit and TestNG. For our examples, we will use JUnit. JUnit 4's approach is highly annotation-based. The @Test annotation is all you need to add to define a test:

package com.prospringhibernate.gallery.test;

import org.junit.Test;
import org.junit.Assert;

public class TrivialJUnitTest {

    @Test
    public void testSimpleStuff() {
        String name = "ProSpringHibernate";
        Assert.assertEquals("ProSpringHibernate", name);
    }
}

A couple of additional basic JUnit annotations can help define the life cycle of the test. You can run some code immediately before and after each test method using the @Before and @After annotations. Guess which one comes before a test? You can also run code before and after all tests in a particular class using @BeforeClass and @AfterClass. (Note that the @BeforeClass method must be static.) There's also an @Ignore annotation, which allows you to use a @Test annotation and not run a particular method.

Of course, the main point of a test is to set up a scenario, and then verify a group of assertions. JUnit provides several built-in assertions, such as verifying that two values should be equal, a returned value is not null, and so on. You'll notice many of these annotations in the following example.

package com.prospringhibernate.gallery.test;

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

public class SimpleJUnitTest {

    public static String staticName = null;
    public String memberName = null;

    @BeforeClass
    public static void initializeClass() {
        staticName = "Rod Johnson";
    }
@Before
    public void initializeTest() {
        memberName = "Gavin King";
    }

    @Test
    public void simpleEqualsAssertion() {
        Assert.assertEquals("Rod Johnson", staticName);
    }

    @Test
    public void simpleBooleanAssertion() {
        Assert.assertFalse(staticName.equals(memberName));
    }

    @Test
    @Ignore
    public void dontTestThis() {
        // notice that this would fail without @Ignore
        Assert.assertEquals("Rod", memberName);
    }

}

Now let's move beyond the basics and apply some tests to our art gallery application.

Unit Testing with Mocks

The tests in the previous examples are fairly simple, in that they don't have any dependencies on either the Spring container or implementations of other classes or components. Because our tests are isolated to a specific class, they qualify as unit tests. Most of the time, you'll need to go beyond such basic testing to simulate the effects of two or more classes interacting with each other. Integration tests are one way to achieve this, but that generally entails a fair amount of code and tight coupling of tests. An alternate strategy is to use stubs or mocks.

Stubbing and mocking both attempt to simulate the behavior of a particular component or layer within an application, without relying on an actual, full-featured implementation. This approach helps to focus your testing concerns on the code actually being tested, rather than the details of other layers.

Stubbing usually implies that a particular component is faked, with "canned responses" being returned so that the layer being tested is fooled into believing that it is talking to the actual live implementation. Mocking also attempts to simulate a particular layer, but it does more than just return canned responses. A mocked object can also be used to validate expected behavior relating to the layer it is intended to represent. For example, it is possible to specify that a certain method is called on the mock as well as other details that help to provide valuable assertions about how the code you are testing integrates with the mocked layer.

Spring provides several useful mock layers, which can be used as drop-in replacements for various layers within your application. For example, JNDI, Servlet-API, and Portlet mock layers that simulate behavior and associated expectations for their respective layers. For mocking other components, it is possible to use frameworks like jMock, EasyMock or MockObjects. These frameworks provide an extensible means for defining your own mocks dynamically.

For our examples, we will use the very powerful jMock framework. JMock allows you to define the behavior for a particular class, as well as expectations for how particular methods on the class will be called and what they will return within the context of a unit test. jMock employs a simple DSL that allows you to specify a fairly flexible range of behavior. We'll point out a few of the basic jMock concepts when we look at a unit test with mocks later in this chapter.

Let's look at a unit test that attempts to verify the behavior of our ArtworkService implementation:

package com.prospringhibernate.gallery.test;

import com.prospringhibernate.gallery.domain.Person;
import com.prospringhibernate.gallery.exception.AuthenticationException;
import com.prospringhibernate.gallery.service.ArtworkFacade;

import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;

import org.jmock.Mockery;
import org.jmock.Expectations;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;

@RunWith(JMock.class)
public class JMockJUnitTestExample {

    Mockery context = new JUnit4Mockery();

    private Person person;
    private ArtworkFacade artworkService;

    @Before
    public void initializeTest() {
        person = new Person();
        person.setUsername("username");
        person.setPassword("goodpassword");

        // here we use jMock to create a mock based on our Interface
        artworkService = context.mock(ArtworkFacade.class);
    }

    @Test
    public void testAuthenticationSuccess() throws AuthenticationException {

        // define expectations for authenticatePerson method
        context.checking(new Expectations() {{
            allowing(artworkService).authenticatePerson("username", "goodpassword");
            will(returnValue(person));
        }});
        artworkService.authenticatePerson("username", "goodpassword");

    }
@Test (expected=AuthenticationException.class)
    public void testAuthenticationFailure() throws AuthenticationException {
        // define expectations, assuming a bad
        // username/password
        context.checking(new Expectations() {{
            allowing(artworkService).authenticatePerson("username", "badpassword");
            will(throwException(new AuthenticationException()));
        }});
        artworkService.authenticatePerson("username", "badpassword");

}

}

Notice that no external dependencies are required for this unit test. This not only helps to isolate the code we are testing, but also significantly speeds up the test. Recall that our ArtworkService façade depends on several DAOs, including ArtEntityDao and PersonDao, but there's nothing in this code that instantiates those classes before the authenticatePerson method is executed.

We declare that we are using the jMock framework by supplying the @RunWith(JMock.class) annotation. Next, we define the context instance variable, instantiating a JUnit4Mockery instance. We can then use the context instance variable to define the behavior and expectations of our mocked ArtworkFacade.

We create a mocked instance by calling the mock method on our context instance variable, passing in the interface for the type we wish to mock:

artworkService = context.mock(ArtworkFacade.class);

This line sets our artworkService instance to a mocked implementation of our ArtworkFacade interface. This takes care of the setup.

Now we need to delineate the behavior and expectations for the mock we just defined. This is accomplished in a few steps. Expectations are specified by calling context.checking() and passing in an anonymous inner class of type Expectations. The Expectations class provides most of jMock's DSL features, allowing us to more easily express the behavior of each method we intend to mock.

Things get interesting within this Expectations block. We can specify the behavior for each method—even defining different behavior based on different parameters or conditions. There are several options that can be defined in mocking our ArtworkFacade's behavior. However, we must first set the expectations for how our method will be called. Do we expect it to be called exactly once, more than once, within a range, or a specified number of times? Or do we not care whether our method will be called at all? These details are referred to as the invocation count, and represent the first part of the jMock DSL.

In our example, we use the allowing invocation rule, which tells jMock that we don't care too much about the invocation count. If we did care, we might have used oneOf, which implies that we expect the method to be called only once. Or we could have used exactly(5).of() to require that our method be called precisely five times. Similarly, we might have used atLeast(5).of() to insist that our method be called at least five times. jMock will automatically fail the test if the expectations you specify are not met within the unit test.

So far, our DSL structure looks like the following:

Invocation-Count(mockInstance).method(expectedParams);

In this structure, we specify the expected parameters our mocked method should receive. You can actually define more than one rule, each specifying a different set of parameters. In fact, you don't even need to specify concrete parameters. You can instead specify matchers, which can be used to define more generalized conditions for a particular behavioral rule. The role of a matcher is to allow for the expression of flexible conditions for the expectations of a mocked method. Matchers must be nested within a with clause:

atLeast(5).of(artworkService).
  authenticatePerson(with(any(String.class)), with(any(String.class)));

This rule states that authenticatePerson must be called at least five times, with two parameters that must be of type String. In this case, we are using the any matcher, which allows us to specify the type that the parameter must match. There are several other matchers—such as aNull, aNonNull, not, and same—and you can even define your own matchers.

So, we've defined the basic expectation of a mocked method. But how do we express a mock's behavior? As you've probably inferred from our example, jMock allows us to define the behavior for a particular "rule" by specifying a will call after our expectation. We define two possible outcomes for our authenticatePerson method. The first expectation defines a "good" username and password. We follow this expectation with the following:

will(returnValue(successfulPerson));

This will ensure that a successfulPerson instance (defined earlier) will be returned whenever pfisher and goodPassword are provided to the authenticatePerson method in our unit test.

Similarly, we define another expectation that assumes an invalid username and password. For this variant, we specify this will call:

will(throwException(new AuthenticationException());

This will ensure that an AuthenticationException is always thrown in our unit test, whenever an invalid username and password is provided to the authenticatePerson method (as long as the invalid username and password match our very stringent conditions, which require that they be username and badpassword).

A few other variations can be used with the will call, such as returnIterator and doAll, to provide more flexibility in defining the behavior for our mocked method given a set of conditions. There are also a few more advanced features of jMock that allow you to constrain a set of calls to a particular sequence, or to work with a state machine and verify certain assumptions about the state as it changes from one call to the next. See the JMock documentation to learn more.

Note

We encourage you to explore mocking in more detail, as it is a powerful tool in the creation of an effective unit-testing strategy. Often, developers focus too much on integration testing, simply because unit testing can seem more complicated when "faking" the behavior of dependencies becomes necessary. Mocking is a pragmatic solution to easily defining behavior and expectations for dependencies, encouraging the development of more unit tests.

We've presented a cursory overview for unit testing a piece of code in isolation. However, an effective testing strategy also needs to take into consideration how a particular component works within the context of a running application. This is where integration testing comes in, and Spring serves to simplify the development of integration tests by helping to bootstrap your test suite, resolving dependencies, and handling persistence details, such as transactions.

Spring Dependency Injection and Testing

The Spring Framework provides a convenient layer of abstraction that drastically simplifies switching between these testing frameworks called the TestContext Framework. This framework, which was added as a part of Spring 3, helps to abstract away any test framework-specific details. By using Spring's TestContext Framework, you no longer need to worry about the specifics of a particular testing framework. This makes it especially easy to jump from one testing strategy to another. But more important, the TestContext Framework serves to simplify testing details, making it easier to not only write effective unit and integration tests, but also to integrate Spring dependencies and make useful assertions related to your persistence tier.

Spring testing includes a combination of XML and annotations to affect the way dependencies are injected within a test class. XML configuration works in a similar fashion to the examples you've seen earlier in this book. When defining integration tests, you can use the same XML code (more or less) that you use in your application. However, it is often desirable to override certain beans, such as your datasource, while maintaining the same wiring and implementation details for your application DAOs and service objects. Such a strategy enables you to verify the behavior of your entire persistence tier, while leveraging a specialized test database.

Quite a few Spring-specific annotations are available to get the test configuration stored in your application and the test XML into a running JUnit test. The @RunWith annotation allows you to specify the test framework you would like to use. As mentioned earlier, one of the primary benefits of using Spring's TestContext Framework is that it allows you to define a test class without tying your code to a particular test framework. You can specify that a particular class is in fact a test by using the @Test annotation. Then, to indicate which test framework should be used to run the test, you can use @RunWith, which allows you to specify which test framework to run. For the examples in this chapter, we're going to stick with JUnit 4. We place the following annotation at the top of our test class:

@RunWith(SpringJUnit4ClassRunner.class)

If we wanted to switch to TestNG, we could do that by simply changing the value of this annotation. Adding the @RunWith annotation to your unit test class will bring Spring into the picture for running the tests and wiring the necessary dependencies. However, there are several options for how the wiring details can be specified. The strategy you choose depends on the unique needs of your application. For instance, if you are building a simple application with only a single datasource, then you can go with an autowiring-by-type strategy, which will implicitly inject the class that matches the type specified in the setter method on which the annotation is added. However, if your application uses multiple datasources, then an autowiring-by-type approach isn't as trivial. For those scenarios, you should use the @Resource or @Qualifier annotations, in order to disambiguate the dependency you would like injected.

Though it is usually preferable to let Spring handle dependency injection via configuration or annotation, it's also possible to make your test class implement ApplicationContextAware or to extend AbstractJUnit4SpringContextTests, which gives you direct access to the ApplicationContext, from which you can do a lookup using the bean name:

context.getBean("datasource");

So now you have a handle on some of the options for injecting the layers on which your test class depends. The question that remains is how to inject. In our gallery application, we have defined Spring beans in an XML file named spring-master.xml, which in turn imports our spring-persistence.xml configuration. We can import this configuration by adding the following annotation:

@ContextConfiguration(locations = {"classpath:/META-INF/spring/spring-master.xml"})

The @ContextConfiguration annotation defines the locations of your configuration files. You can determine, on a test-by-test basis, whether to use the Spring configuration file for your full-blown application or to define a more specific unit-testing configuration that is tailored for the needs of a particular test.

Spring configuration via XML is handy, but now you're probably wondering how you can access some beans that are defined in your configuration or those that were picked up through component scanning. Do you remember the @Autowired annotation that Spring managed beans can use? You can use it in your test code to tell the Spring JUnit Runner that you need some Spring beans.

Here's what the PersonDAO test code looks like when we put all of this together:

package com.prospringhibernate.gallery.test;

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

import com.prospringhibernate.gallery.dao.PersonDao;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-master.xml"})
public class PersonDaoTest {

    @Autowired
    PersonDao personDao;

    @Test
    public void testPerson() {
        // insert test logic here
    }

}

Let's explore what's happening here. @RunWith tells JUnit that the test needs some extra logic in order to be set up properly. That extra logic comes in the form of an instance of a class that implements JUnit's Runner interface. In our case, we have a Spring Runner called SpringJUnit4ClassRunner that knows how to set up our application context and inject our test with all of the plumbing that it needs using the standard Spring dependency-injection annotations, such as @Autowired. SpringJUnit4ClassRunner also looks for some other annotations, including @ContextConfiguration and @Transactional.

As you saw in the example, @ContextConfiguration tells SpringJUnit4ClassRunner which configuration files you need to set up your testing environment. Behind the scenes, SpringJUnit4ClassRunner sets up and manages a Spring application context for your unit tests based on the locations you specified in the @ContextConfiguration. The TestContext Framework is responsible for actually performing the @Autowired injection. The TestContext Framework also keeps track of the results of the status of the current test, such as which method and class were run and which exception was thrown as part of the test. @RunWith and @ContextConfiguration are the essential core components of Spring JUnit4 testing.

Note

The TestContext Framework has some performance optimizations to make sure that the framework will load that configuration only once for all of the tests if you run multiple test classes that use the same application context configuration. There are quite a few additional advanced features relating to the TestContext Framework that are worth exploring if you need more advanced testing.

Testing with a Database

Now that you know how to write a JUnit test class and configure it with Spring XML, you're ready to do some database testing! The simplest form of database testing can be to just reuse those fancy DAOs that you've been working on. You can also apply the usual Spring @Transactional annotations, along with another annotation:@TransactionConfiguration.

@TransactionConfiguration tells the transactional Spring testing environment information about how to get the transactionManager and whether you would like to commit or roll back the transaction after each test.

The following test takes all of those elements and puts them to work:

package com.prospringhibernate.gallery.test;

import java.util.List;

import junit.framework.Assert;

import org.junit.After;
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
Import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import com.prospringhibernate.gallery.dao.PersonDao;
import com.prospringhibernate.gallery.domain.Person;
import com.prospringhibernate.gallery.exception.AuthenticationException;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/META-INF/spring/spring-master.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional()
public class TestContextJUnitIntegrationTest {

    Person person;
    PersonDao personDao;

    public PersonDao getPersonDao() {
        return personDao;
    }

    @Autowired
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    @Before
    public void preMethodSetup() {
        person = new Person();
        person.setFirstName("First");
        person.setLastName("Last");
        person.setUsername("username");
        person.setPassword("goodpassword");
        person.setRoleLevel(Person.RoleLevel.ADMIN.getLevel());
        person.setVersion(1);
        personDao.save(person);
    }

    @After
    public void postMethodTearDown() {
        personDao.remove(Person.class, person.getId());
        person = null;
    }

    @Test
    public void testPersonPersisted() {
        final List<Person> people = personDao.getAll();
        Assert.assertEquals(1, people.size());
    }

    @Test
    public void testAuthenticationSuccess() throws AuthenticationException {
        Person p = personDao.authenticatePerson("username", "goodpassword");
        Assert.assertNotNull(p);
    }

    @Test (expected=AuthenticationException.class)
    public void testAuthenticationFailure() throws AuthenticationException {
        personDao.authenticatePerson("username", "badpassword");
    }

}

This example is using our web application's Spring configuration as defined in spring-master.xml, which means that we're using our H2 database configuration. With the @Before and @After annotations, we're ensuring that the state of the Person class is correct for each test method invocation. Finally, in nice discrete units, we test the behavior of our PersonDao when making a successful call to getAll() Person entities, a successful call to authenticate against the database, and lastly, a failed attempt to authenticate against the user data that we have scaffolded in with the preMethodSetup() method.

Notice also that we have set defaultRollback = true, which will ensure that this method is automatically rolled back after completion. Automatically rolling back your transactions within an integration test is an effective strategy for ensuring that each test method returns the database to its original, pristine state. When defining an integration test that talks to a database, it is important to reduce the potential for database "side-effects" and to ensure that each test stands alone, without being affected by or relying upon previously run methods.

Summary

This chapter introduced some testing strategies for Spring applications. Of course, you can do a lot more to test with both JUnit and Spring, not to mention the really powerful testing constructs that are made possible by the mocking frameworks we mentioned. Also, topics such as performance testing and load testing are extremely relevant to persistence.

For more advanced integration testing with databases, we recommend you check out the DbUnit JUnit extension. DbUnit provides excellent facilities for ensuring that your database is in a known state in between tests, as well as tooling to aid in assembling fixture data that can be used across an entire suite of tests.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.254.44