isolated unit testing of persistence with derby - ibm · pdf fileisolated unit testing of...

29
Isolated unit testing of persistence with Derby Skill Level: Intermediate Guy Davis ([email protected]) Development Team Lead Intervera Data Solutions 11 Oct 2005 Get help developing your database-driven unit-testing skills. Without isolation and automation, database-driven unit tests can become a maintenance nightmare of sporadic failures and bad test data. The open source Apache Derby database is ideal for completely automating and isolating unit tests, because it offers fast, in-process performance and zero administration. This tutorial gives you step-by-step examples on using Derby to write effective and maintainable unit tests that improve the quality of your application. Section 1. Before you start The tutorial is for you if you're a software developer who needs to add data-centric unit tests to your project or improve your current database-driven tests. You'll learn how to effectively use Derby to overcome the drawbacks common to other approaches for unit-testing application behavior requiring persistence (data storage). The tutorial provides practical examples for unit testing a class-handling persistence to help you gain unit-testing experience with an embedded database. Refactorings are also applied to correct common problems encountered when writing database-driven unit tests. About this tutorial This tutorial describes how the Derby embedded database can be easily included in your project's unit-testing efforts. You'll use Derby, along with the JUnit and DbUnit testing frameworks, to create isolated, repeatable, and maintainable unit tests. The tutorial covers the following topics: Using the JUnit and DbUnit test frameworks, which greatly assist in unit Isolated unit testing of persistence with Derby © Copyright IBM Corporation 1994, 2008. All rights reserved. Page 1 of 29

Upload: buithien

Post on 11-Mar-2018

220 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Isolated unit testing of persistence with DerbySkill Level: Intermediate

Guy Davis ([email protected])Development Team LeadIntervera Data Solutions

11 Oct 2005

Get help developing your database-driven unit-testing skills. Without isolation andautomation, database-driven unit tests can become a maintenance nightmare ofsporadic failures and bad test data. The open source Apache Derby database is idealfor completely automating and isolating unit tests, because it offers fast, in-processperformance and zero administration. This tutorial gives you step-by-step exampleson using Derby to write effective and maintainable unit tests that improve the qualityof your application.

Section 1. Before you start

The tutorial is for you if you're a software developer who needs to add data-centricunit tests to your project or improve your current database-driven tests. You'll learnhow to effectively use Derby to overcome the drawbacks common to otherapproaches for unit-testing application behavior requiring persistence (data storage).The tutorial provides practical examples for unit testing a class-handling persistenceto help you gain unit-testing experience with an embedded database. Refactoringsare also applied to correct common problems encountered when writingdatabase-driven unit tests.

About this tutorial

This tutorial describes how the Derby embedded database can be easily included inyour project's unit-testing efforts. You'll use Derby, along with the JUnit and DbUnittesting frameworks, to create isolated, repeatable, and maintainable unit tests.

The tutorial covers the following topics:

• Using the JUnit and DbUnit test frameworks, which greatly assist in unit

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 1 of 29

Page 2: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

testing

• Using Derby and Eclipse plug-ins to work with databases

• Creating test cases quickly and easily using the Eclipse IDE

• Integrating the Derby database with DbUnit using theDatabaseTestCase class

• Writing basic unit tests to test class-level behaviors using test assertions

• Running and debugging these unit tests within the Eclipse IDE

• Improving and refactoring the unit tests

Prerequisites

You need previous experience with the Java™ Database Connectivity (JDBC) APIand the Eclipse IDE to complete this tutorial. Exposure to the JUnit and DbUnit testframeworks is helpful but not required; the tutorial provides a brief overview of bothbefore diving into test writing.

System requirements

This tutorial requires:

• The Eclipse IDE (Version 3.0 or higher) to work with the sample project.

• Apache Derby from the Apache Software Foundation.

• The Sun Microsystems Java Development Kit (JDK) (Version 1.4.2 orhigher) to run both Eclipse and the project unit tests.

Section 2. Example project overview

This section details the example project you'll create.

Create an example project

For your example project, there is a PERSON database table at a sports storeholding details about customers. A few pieces of information, such as name,address, and favorite team's name, are held for each customer. Within the exampleapplication, each row of the database corresponds to an instance of the Personentity class.

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 2 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 3: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

You'll test a persistence class named PersonPersist, which manages access tothe Person entity class. Because the main purpose of this tutorial is to developunit-testing skills, the application classes (Person and PersonPersist) have beenwritten for you. Your task is to write unit tests that ensure that reading, adding,updating, and deleting Person entries from the database work properly.

While this is a simple example of JDBC persistence in the Java world, it is sufficientto illustrate the steps involved in configuring Derby to be the test database for use bythe DbUnit test framework.

For real systems, consider the Hibernate persistence manager. The upcomingunified persistence standard being defined as part of Enterprise JavaBeans™ (EJB)3.0 should be considered during new development. These new persistencemechanisms, along with existing application-specific approaches, are all amenableto unit testing with Derby, because they typically rely on JDBC for databasecommunications.

Open the example project

To effectively follow along with this tutorial, you need to download the .zip filesassociated with this tutorial that contain multiple Eclipse project directories. TheStage X directories contain the incomplete project, which you'll work with during thetutorial. You start by opening the Stage 1 project. The other directories contain theproject in various stages of completion, with the Complete directory representing thecomplete project at the end of the tutorial.

Follow these steps to open the sample tutorial project:

1. Download the stage1.zip file, and uncompress it onto your workstation(C:\tutorial, for example).

2. Start the Eclipse IDE, and select File > New > Project.

3. Choose Java Project as the type of project to create.

4. Click the Next button.

5. Give the project a name, such as Unit Testing with Derby.

6. Choose Create a project from existing source.

7. Specify the Stage 1 directory as the content's directory.

8. Click the Finish button.

Note: For the purposes of this tutorial, you only need to download stage1.zip.However, .zip files containing later stages of the project are available to download(see the Download section) if you want to compare your code with what was writtenat that stage and/or you have problems with a previous section and want to start

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 3 of 29

Page 4: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

from a known point.

Eclipse should open the existing project and display the resources it contains(shown below):

• lib/ contains the derby.jar and dbunit2.1.jar libraries.

• src/ contains the sample application code you'll test.

• test/ holds your JUnit test cases as you add them.

• sql/ holds your table schema and test data set files.

Before jumping into this code, let's discuss other approaches to unit testing involvinga database.

Section 3. Painless unit tests

Why use an embedded database for unit testing? This section explains the benefitsof this approach for testing the persistence behavior of your application. It alsocovers other common approaches for unit testing with databases and theirdrawbacks.

Motivation for using an embedded database

While many development frameworks today provide nearly transparent data storageservices, you still need to test the correctness of the persistence layer for yourapplications. Testing is critical to finding errors whether the application needs tosupport databases from different vendors or just different versions of the samedatabase. Unit tests are an excellent way of ensuring that interactions between yourbusiness components and the data they rely on are correct.

Often, you might need to test an application with tight coupling between thedatabase and the business logic, meaning a real database is absolutely required. Agood example of this is the use of stored procedures residing in a database toprovide application-level behavior. To fully test this behavior, the unit tests mustconnect to the database to access the stored procedure's functionality.

In a development team environment, the Derby embedded database can provide allthe necessary behavior of the production database, yet it can be run on eachdeveloper's workstation. By isolating the test environment to each developer, the unittest suite is made more durable and repeatable. A zero-administration database, likeDerby, eliminates the need for installing, configuring, and maintaining multipledeveloper editions of the production database.

In the projects I've worked on, I've seen a number of different approaches to unit

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 4 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 5: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

testing of the persistence layer for a client/server application. Unfortunately, eachapproach manifested serious drawbacks after the technique was adopted. Let's firstconsider the use of a single, central database for all unit testing.

Single master database for all developers

Often, applications are built to rely on a central database during production use. Thiscan routinely result in unit tests that depend on a development instance of thedatabase on a single server. While this is an easy approach initially to getdata-centric unit tests written, it's certain to become a problem in the future for thefollowing reasons:

• As more developers join a project, the likelihood of the unit tests being runconcurrently on different systems increases. Unit tests that run on multiplesystems against a common data set are likely to result in sporadic testfailures that are difficult to diagnose, reducing developer productivity.

• Unit tests that rely on a central database take longer to run than testsusing a local database on the workstation. For unit tests to be effective,they need to be run often and quickly, or developers will begin to ignorethem.

• In my experience, unit tests that run using a central database are betterdescribed as integration tests. Typically, these tests exercisecollaborating components rather than testing individual units (at theclass/method level). While integration tests are important, they should notbe used in place of unit testing.

Many large central databases allow the configuration of instances, which helpsisolate developers from each other. However, this approach still requiresadministration to set up and maintain these instances. And, if the database serverfails, your developers are all waiting around for it to come back up.

If, instead, you isolate the code and data to individual developer systems, thedevelopment team can work independently of any shared resources. This can be anefficient approach when combined with a continuous integration process forintegrating the source code changes from all developers.

Install a full database on a developer's workstation

An alternate approach to avoid the problem of concurrently accessing data on acentral database is to install a separate database on each developer's workstation.Unfortunately, many production databases require a significant amount of resourcesand configuration, even if they have a development version.

This adds to the setup and maintenance of components required for a developerworkstation, detracting from developer coding time. Also, the resources (memoryand CPU time) required for the database are used at the expense of the IDE and the

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 5 of 29

Page 6: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

application being developed.

A more effective approach would completely automate the setup and use of thedatabase on a developer's system. Ideally, simply checking out the project fromversion control, building it, and running the unit tests would be enough to create anyneeded local databases and tables. While the developer could interact with the localdatabase, he or she wouldn't be expected to maintain it.

Replace the database with a mock object framework

Another approach used in the industry is to replace the dependency on a databasealtogether. By using a mock object framework, you can simulate the behavior of adatabase using in-memory Plain Old Java Objects (POJOs). This approach has thebenefit of avoiding the configuration complexity and resource usage required for atraditional database installation.

However, it quickly becomes evident that a mock set of database connections,statements, and result sets work for only simple data relationships. Often, emulatingdatabase behavior with mock objects can require more work than using anembedded database.

Some situations make the use of a mock object in place of a real JDBC databaseimpractical. Stored procedures are a good example of this, as the stored proceduremay cover complex behavior that cannot easily be emulated by a mock object. If thestored procedure logic will be used in a production system, it must be tested directlyin a database to ensure correctness.

Section 4. Working with Derby

Now that you've considered the drawbacks of other approaches to unit testingpersistence functionality, let's look at the Derby embedded database. Afterintroducing the database, you'll learn about the easy installation process. You'll alsolook at some of the command lines and graphical tools available for working withDerby.

Introducing the Derby database

The Derby database is an open source project based on the IBM Cloudscape™database (see Resources for a link to the download), which has been activelydeveloped since 1997. The Derby database is implemented in pure Java languageand boasts a small disk and memory footprint, making it ideal for bundling andembedding with other Java applications requiring a database.

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 6 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 7: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Derby offers transaction management, concurrency, triggers, and online backups.And it's compliant with the JDBC and SQL standards. Command-line utilities andgraphical interfaces are available for working with Derby in both embedded andnetworked modes.

In embedded mode, the database can be accessed only within a single instance ofthe Java Virtual Machine (JVM). However, in network mode, the database is alsoaccessible through TCP/IP for connections from other systems. You'll use theembedded mode for this tutorial, because only the unit tests need access to the testdatabase.

Install Derby

Installing Derby is particularly simple, because only a single .jar file is required foruse in embedded mode. For the purposes of this tutorial, you'll find the derby.jar filein the lib directory of the project. The Eclipse project file already includes this .jar filein the project class path making it possible to use the Derby database.

For this project, it's not necessary to create a DERBY_INSTALL environmentvariable on your system. If you want to try the command-line tools after completingthis tutorial, follow the directions found in the Derby documentation (see Resourcesfor a link to the documentation).

Note that the Derby distribution, available from Apache's Web site (see Resourcesfor a link), also includes the network server components and utilities. However, youwon't be using Derby in its network server mode during this tutorial.

Derby command-line tools

Derby provides a tool called sysinfo that's used to verify that your class path is setcorrectly before invoking a program using a Derby database. For example, to test ifMyApp.class would use Derby in embedded mode, you could check the class pathwith:

java org.apache.derby.tools.sysinfo -cp embedded MyApp.class

The Derby database also comes with a command-line tool known as ij that allows forSQL scripts to be run against embedded and network Derby databases. Forexample, you could run a script against a test database using:

java -Djdbc.drivers=org.apache.derby.jdbc.EmbeddedDriverorg.apache.derby.tools.ij <myscript.sql>

Assuming your class path is properly configured and was tested using sysinfo,use this command to run the myscript.sql file. (For more information about the ijtool, including the full command reference, link to the "Tools and Utilities Guide" bygoing to the Resources section).

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 7 of 29

Page 8: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Eclipse database utilities

Another useful tool for working with the Derby database is the Quantum DB plug-in,available free for Eclipse. Using the Quantum DB perspective in Eclipse, you canview and edit data from a wide variety of databases. This is especially useful forworking with unit test data in Derby and accessing other data in your productiondatabase, such as Oracle or Microsoft® SQL Server, because the interface remainsthe same. Figure 1 shows the schema and test data containing the PERSONdatabase table for your unit tests.

Figure 1. Quantum DB view of the PERSON table

A number of other useful plug-ins for Eclipse allow interaction with databases. TheEclipse plug-ins site (see Resources for a link) does a good job of summarizing thevarious options.

Section 5. Overview of Java unit testing

This section examines JUnit, the most commonly used framework for unit testing inJava. It also covers how DbUnit builds upon JUnit by providing extra functionalityspecifically for testing with databases.

JUnit framework

Written by Kent Beck and Erich Gamma in the late 1990s to provide a useful

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 8 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 9: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

framework for writing unit tests in the Java language, JUnit has become widelyadopted by Java programmers everywhere. The framework's principal component isthe test case, which is typically used to hold tests for a particular class from theapplication test code. A test method name begins with test, which JUnitautomatically discovers and executes through Java's reflection API. Test cases areaggregated into test suites.

Test fixtures are defined as the set of objects required to run the test. Often fixturesconsist of utility methods that hold common behaviors used for a set of test cases. If,for instance, a particular setup of data and application objects was required for manytest cases, fixtures could handle the work.

Integration with Eclipse

JUnit is well integrated with the Eclipse IDE. In Eclipse 3.1, you can:

• Create a test case for an application class.

• Run/debug JUnit tests at the package level per test case, or even pertest.

• See the output of the test run, with a green bar for success and a red barfor test failures.

• Find all referring tests for a particular application method by selectingSearch > Referring Tests.

Detailed examples of how to use JUnit within Eclipse are shown when you test forpersistent data classes later in this tutorial.

The JUnit view within Eclipse, shown in Figure 2, indicates that the three tests withinthe PersonPersist test case have passed successfully, as a green bar is displayed.Press Alt-Shift-X, then press T to run the selected package or test case.

Figure 2. JUnit view showing test results

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 9 of 29

Page 10: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

When a test method fails, JUnit displays a red bar to indicate a problem. In Figure 3,you see from the trace back that an assertion failed. In this case, the Personretrieved from the database was named Bob Jones, while the unit test expectedSam Bishop.

Figure 3. JUnit view showing a failed assertion

DbUnit test framework

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 10 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 11: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

While the JUnit framework is useful for general-purpose unit testing, DbUnit hasextended JUnit for tests using the JDBC API. DbUnit provides an easy starting pointto cleanly load test data sets before running each test case. The data sets can bespecified in a simple .xml file or by a number of other approaches.

By ensuring that a database is in a known state before each test is run, DbUnitremoves the sporadic test failures that result from tests relying on an existing set ofdata, which may be modified through test runs. In particular, the outcome of the testsshould be independent of the order in which they are run.

DbUnit defaults to loading a data set by doing a clean insert, which truncates thetable before inserting. In some cases, other behavior is desired so DbUnit allows forthe data set to update, insert, delete, delete all, truncate, or refresh the target table.

Unit test persistence data classes

Now it's time to write some unit tests using your understanding of Derby, JUnit, andDbUnit. Here's the approach:

1. Review the PersonPersist class to decide what to test.

2. Use Eclipse to create a test case stub to fill in.

3. Implement DatabaseTestCase to integrate Derby with your tests.

4. Create some test data for the Person table in DbUnit's XML format.

5. Add unit tests that exercise each of the methods in the PersonPersistclass.

After this example, you should have an understanding of how the Derby embeddeddatabase allows the effective testing of an application by isolating the database tojust the developer's JVM. Let's get to it!

Work with the Person entity (Stage 1)

To begin, switch back to Eclipse and open the Stage 1 project, following theinstructions at the beginning of the tutorial. Look at thecom.example.entities.Person class that holds various attributes, such asname, address, and favorite team's name. Getter and setter methods are providedfor each attribute.

This entity class is accessed through its persistence manager class:com.example.persist.PersonPersist. This persistence class provides utilitymethods for creating, updating, retrieving, and deleting Persons from the databaseaccessed through a Connection object. It's important to review the methodsignatures this class exposes, as shown in Listing 1, as these will be tested by your

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 11 of 29

Page 12: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

test case.

Listing 1. Method signatures exposed by thecom.example.persist.PersonPersist class

public void createPerson(Person person);

public Person getPersonByName(String name);

public Person getPersonById(int id);

public void updatePerson(Person person);

public void deletePerson(Person person);

Next you write a test method for each of these public methods to cover thefunctionality of the class. Normally, to get adequate coverage of a method, you needmultiple tests to cover the various cases and possible exceptions.

Create the test case

Eclipse provides a wizard for quickly creating new test case classes for yourapplication classes. Simply select thecom.example.persist.PersonPersistTest class, and choose File > New >JUnit Test Case. Figure 4 shows the first step in this process, where the classunder test is selected. Because you're using DbUnit, change the Superclass toorg.dbunit.DatabaseTestCase. To save you some effort, allow Eclipse tocreate method stubs for setUp() and tearDown() .

Figure 4. Creating a test case in Eclipse (Step 1)

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 12 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 13: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Click Next to view the next dialog, shown in Figure 5. Select the methods from thecom.example.persist.PersonPersist class that you'll test.

Figure 5. Creating a test case in Eclipse (Step 2)

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 13 of 29

Page 14: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Click Finish to have Eclipse create the test case class. At this point, the new classshould appear in the com.example.persist package of the test/ source directory.

Implement DatabaseTestCase

The new PersonPersistTest class has two problems displayed in Eclipse at thispoint, as shown in Figure 6.

Figure 6. Build problems for the new test case

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 14 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 15: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

These two methods, getConnection() and getDataSet(), are required to makethis a valid DatabaseTestCase. The first method is a means of providing DbUnitwith access to the Derby test database, as shown in Listing 2.

Listing 2. Providing DbUnit with access to Derby test database

/** Obtains the Derby connection you'll be using solely for testing. */protected IDatabaseConnection getConnection() throws Exception {

Class.forName("org.apache.derby.jdbc.EmbeddedDriver");Connection jdbcConnection = DriverManager.getConnection(

"jdbc:derby:derbydb;create=true", "", "");return new DatabaseConnection(jdbcConnection);

}

In this case, you use the Derby embedded driver to access a database namedderbydb with an empty username and password. The database is created the firsttime it's accessed. This short method is all that's required to use Derby for unittesting as an embedded database.

The second method is used to specify the sample data that's loaded before the unittests run, as shown in Listing 3.

Listing 3. Specifying the sample data to be loaded before running unit tests

/** Specifies the XML file with a set of sample person table data */protected IDataSet getDataSet() throws Exception {

return new FlatXmlDataSet(newFileInputStream("sql/data/person.xml"));

}

By specifying a FlatXmlDataSet, you can quickly add a small set of sample datainto the Person table by writing a bit of XML, which you'll write in the next stage.

You're welcome to continue with your existing project. If you have problemscompleting the next stage, take a look at the contents of the Stage 2 project, whichshould resemble your current project.

To be useful, DbUnit needs a data set to load into your database before every test inyour test case is run. To test the PersonPersist class, you load three distinctpeople into your Person table. DbUnit allows a variety of formats to be used whendeclaring data sets. A simple one is the XML file shown in Listing 4.

Listing 4. A simple XML format for declaring data sets

<?xml version='1.0' encoding='UTF-8'?><dataset>

<person person_id='1' team_name='Falcons' name='Sam Bishop'address='22 Rose Street'/>

<person person_id='2' team_name='Hawks' name='Joe Mama'address='1932-43rd Street'/>

<person person_id='3' team_name='Raptors' name='Sally Dobson'address='#100, 68 Morris Road'/>

</dataset>

Save this file to the sql/data directory as person.xml. The format uses a tag for each

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 15 of 29

Page 16: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

row being added to the table. Each attribute name corresponds to the name of thecolumn that holds the data value.

One thing is missing at this point. DbUnit does not automatically create any requiredtables for testing. In many cases, this table creation is done when the schemas arechanged and doesn't need to be done for every test run.

In the case of this simple example, you add support for creating the Person tableeach time. Let's write the CREATE TABLE statement into a file calledsql/schemas/person.sql, shown in Listing 5.

Listing 5. Writing the CREATE TABLE statement

CREATE TABLE person (person_id INT PRIMARY KEY,name VARCHAR(50),team_name VARCHAR(255),address VARCHAR(255)

)

Run the SQL command

Now you need to ensure that this SQL will be run before DbUnit tries to load yourtest data set during the DatabaseTestCase setUp() method. This can beaccomplished by overriding the setUp() method with your own, as shown in Listing6.

Listing 6. Overriding the setUp() method

public void setUp() throws Exception {try {

this.connection = getConnection().getConnection();createTable(this.connection, "person", new File(

"sql/schema/person.sql"));} catch (Exception e) {

e.printStackTrace();}super.setUp(); // Loads the data set for us

}

This setup() opens a connection, which is used in your test methods. It's first usedin the call to createTable(). You should add a corresponding tearDown()method to close the connection opened in setUp(), as shown in Listing 7.

Listing 7. Adding a tearDown() method to close the connection opened insetUp()

public void tearDown() throws Exception {super.tearDown();try {

this.connection.close();} catch (Exception e) {

e.printStackTrace();}

}

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 16 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 17: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

After adding the setup() and tearDown() method, Eclipse should show thethis.connection variable underlined in red. Place the cursor on this variable,and have Eclipse open the Quick Fix menu by pressing Ctrl-1. The Quick Fixcontextual menu appears, as shown in Figure 7.

Figure 7. Quick Fix view to create a field

You're welcome to continue with your existing project. If you have problemscompleting the next stage, take a look at the contents of the Stage 2 project, whichshould look similar to your current project. Follow the directions at the beginning ofthe tutorial to create a new Java project from the Stage 2 directory to compare withyour work.

Select the first choice in the menu to have Eclipse create the connection variable foryou. You may need to change the type of the new variable to Connection fromObject so you have private Connection connection;.

Add a createTable() method

The next step is to add a method called createTable(), shown in Listing 8, whichwill drop an existing table if it exists before creating a fresh one from your schemadefinition.

Listing 8. Adding a createTable() method

private void createTable(Connection conn, String tableName,File tableSchemaFile) throws SQLException, IOException {

try {Statement st = conn.createStatement();st.execute("DROP TABLE " + tableName);st.close();

} catch (Exception e) {}

Statement st = conn.createStatement();String sql = loadSqlFromFile(tableSchemaFile);st.execute(sql);st.close();

}

The method handling the execution of the CREATE TABLE statement saved earlieris shown in Listing 9:

Listing 9. Handling the execution of the CREATE TABLE statement

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 17 of 29

Page 18: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

private String loadSqlFromFile(File tableSchemaFile) throws IOException{BufferedReader reader = new BufferedReader(new InputStreamReader(

new FileInputStream(tableSchemaFile)));StringBuffer buf = new StringBuffer();while (reader.ready()) {buf.append(reader.readLine());

}return buf.toString();

}

At this point, the following steps occur during execution for each of the tests:

1. setUp() opens a connection for use in creating the Person table,loading the CREATE TABLE statement in sql/schema/person.sql.

2. super.setUp() does a clean insert of the data set contained insql/data/person.xml.

3. The test framework runs a test method, such as testCreatePerson(),which either passes, fails, or results in an error.

4. tearDown() runs, which closes the open connection and callssuper.tearDown().

The sample project you're working on should now compile. If not, take a look at theStage 3 project that contains the code at this point (see the Download section forlinks to the source code).

Test getPersonById()

Now that you have a test case and have defined a data set that loads for each test,you can test your persistence methods. For this test, you can attempt to retrieve theperson with an ID number of 1. The test can then ensure that the details of theretrieved person match the expected values.

To begin, let's instantiate a PersonPersist object using the connection field:

PersonPersist personPersist = new PersonPersist(this.connection);

Now specify the expected values for the person with an ID of 1. The expected valuesare those you specified earlier in the .xml data set file. You check their name,favorite team, and address:

String expectedName = "Sam Bishop";String expectedTeam = "Falcons";String expectedAddress = "22 Rose Street";

Next, the test needs to execute the behavior being tested, so callgetPersonById():

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 18 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 19: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Person person = personPersist.getPersonById(1);

Finally, you need the assert methods, which ensure that the expected person wasfound and that their attributes are all correct. These checks are shown in Listing 10.

Listing 10. Verifying the details of the retrieved person match

assertNotNull(person);assertEquals("Expect the same name.", expectedName,

person.getName());assertEquals("Expect correct team.", expectedTeam,

person.getTeamName());assertEquals("Expect correct address.", expectedAddress,

person.getAddress());

Putting the whole test method together, you end up with Listing 11:

Listing 11. The whole test method

public void testGetPersonById() {PersonPersist personPersist = new PersonPersist(this.connection);

String expectedName = "Sam Bishop";String expectedTeam = "Falcons";String expectedAddress = "22 Rose Street";

Person person = personPersist.getPersonById(1);

assertNotNull(person);assertEquals("Expect the same name.", expectedName,

person.getName());assertEquals("Expect correct team.", expectedTeam,

person.getTeamName());assertEquals("Expect correct address.", expectedAddress,

person.getAddress());}

Now let's try running the unit test in Eclipse to see the result. Right-click thePersonPersistTest class in the Package Explorer view, and choose Run As >JUnit Test, as shown in Figure 8.

Figure 8. Starting a JUnit test case run

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 19 of 29

Page 20: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Eclipse should start the JUnit test runner and display the results, as shown in Figure9.

Figure 9. Successful test result from JUnit

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 20 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 21: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

While this test run executed the other five empty tests, it's possible to have Eclipserun a single test. This is particularly useful for debugging test failures.

Test getPersonByName()

Similar to the previous test, now you perform a person lookup by name and thenensure that the person's details are as expected. This time, define the assertionsfirst for your method, as shown in Listing 12.

Listing 12. Defining the assertions

assertNotNull(person);assertEquals("Expect the same name.", expectedName,

person.getName());assertEquals("Expect correct team.", expectedTeam,

person.getTeamName());assertEquals("Expect correct address.", expectedAddress,

person.getAddress());

Now you need to list the expected name, team, and address for this person:

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 21 of 29

Page 22: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

String expectedName = "Joe Mama";String expectedTeam = "Hawks";String expectedAddress = "1932-43rd Street";

Adding an instantiation of your PersonPersist object and calling the methodunder test, you have:

PersonPersist personPersist = new PersonPersist(this.connection);Person person = personPersist.getPersonByName(expectedName);

Putting it all together, as shown in Listing 13, the test method has the threecomponents of expected data, executing the method under test and asserting theresults match the expected values.

Listing 13. The three components of expected data

public void testGetPersonByName() {PersonPersist personPersist = new PersonPersist(this.connection);

String expectedName = "Joe Mama";String expectedTeam = "Hawks";String expectedAddress = "1932-43rd Street";

Person person = personPersist.getPersonByName(expectedName);

assertNotNull(person);assertEquals("Expect the same name.", expectedName,

person.getName());assertEquals("Expect correct team.", expectedTeam,

person.getTeamName());assertEquals("Expect correct address.", expectedAddress,

person.getAddress());}

Test updatePerson()

Moving on to write testUpdatePerson(), you first retrieve Sally by her ID numberof 3. Then you can update her name and persist the change, as shown in Listing 14.

Listing 14. The three components of expected data

PersonPersist personPersist = new PersonPersist(this.connection);Person sally = personPersist.getPersonById(3);assertNotNull(sally);String newName = "Sally Fraser";sally.setName(newName);personPersist.updatePerson(sally);

This name change should now have been persisted down to the database. Byretrieving Sally from the database again, you can verify the name change.

sally = personPersist.getPersonById(3);assertEquals("Expect the same name.", newName, sally.getName());

Together, these pieces combine to give the following test for updatePerson(), as

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 22 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 23: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

shown in Listing 15.

Listing 15. Test for updatePerson()

public void testUpdatePerson() {PersonPersist personPersist = new PersonPersist(this.connection);Person sally = personPersist.getPersonById(3);assertNotNull(sally);String newName = "Sally Fraser";sally.setName(newName);personPersist.updatePerson(sally);

// Now retrieve Sally again to ensure that her name change was storedsally = personPersist.getPersonById(3);assertEquals("Expect the same name.", newName, sally.getName());

}

Test createPerson()

Now try filling the test code for the testCreatePerson() method, as shown inListing 16. You'll want to create a new instance of a person with a person_idgreater than 3, save them to the database, and then try to retrieve them back again.

Listing 16. Filling the test code for the testCreatePerson() method

public void testCreatePerson() {PersonPersist personPersist = new PersonPersist(this.connection);

int person_id = 4;String name = "The New Guy";String team = "Eagles";String address = "123 Fake Street";

Person newPerson = new Person();newPerson.setPersonId(person_id);newPerson.setName(name);newPerson.setTeamName(team);newPerson.setAddress(address);

personPersist.createPerson(newPerson);Person retrievedPerson = personPersist.getPersonById(person_id);assertEquals("IDs should match.", person_id,

retrievedPerson.getPersonId());assertEquals("Names should match.", name,

retrievedPerson.getName());assertEquals("Teams should match.", team,

retrievedPerson.getTeamName());assertEquals("Addresses should match.", address,

retrievedPerson.getAddress());}

Test deletePerson()

Now write a short testDeletePerson() method to verify the behavior of removinga person from the database. You need to retrieve or instantiate a person, deletethem, and then verify they are really removed (see Listing 17).

Listing 17. A possible solution

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 23 of 29

Page 24: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

public void testDeletePerson() {int person_id = 2;

PersonPersist personPersist = new PersonPersist(this.connection);Person person = personPersist.getPersonById(person_id);assertNotNull(person);

personPersist.deletePerson(person);person = personPersist.getPersonById(person_id);assertNull(person);

}

You've now written a test for each of the methods offered by the PersonPersistclass. You should have five tests passing when you run JUnit. If you have anycompilation problems or test failures, try to work through them using the tools inEclipse. Refer to the Complete project directory (see the Download section) to seethe finished set of tests.

Section 6. Improvements and refactorings

The tests you wrote previously in this tutorial were an excellent first experience withunit testing involving a database. However, as with any code, there are always waysof improving or refactoring your code. Refactoring is the process of cleaning upsource code in such a way that its external behavior is not affected. By refactoring,you can improve code efficiency and readability as it is changed.

Let's consider some possible refactorings that could be applied to the exampleproject we just completed.

Test other conditions

The tests presented previously only covered the happy path of code execution. Tobetter test the PersonPersist class, tests that exercise error conditions and otherfailures are required.

For example, as the person_id column is a primary key, trying to insert a new personwith an existing person_id should result in an error. You could write a test to ensurethat the error behavior occurs in this case.

The getPersonByName() method doesn't currently do a good job if there are twoor more different people in the table with the same name. A test-driven approach tosolving this problem is to:

1. Modify the XML data set to load two people sharing the same name.

2. Write a unit test that expects the PersonPersist class to throw anexception if more than one person is found for a given name, and then

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 24 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 25: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

run the new unit test to be sure it fails.

3. Adjust the PersonPersist class to treat two people with the samename as an error by throwing an exception.

While it may seem strange to write a test before fixing the problem in the application,this test-driven development is effective. By writing the unit test first, the userrequirement that this condition generate an error is now a testable behavior. Theapplication is changed to make the test pass after the test suite has codified thisrequirement as a regression test case. By creating a regression test case for everyproblem discovered, the application is sure not to develop this same problem in thefuture, because the test case would catch the error.

Move duplicated code into setUp()

A refactoring is also a means of improving the maintainability of source code. Onecommon refactoring is the removal of duplication. You may have noticed that each ofthe test methods begins with:

PersonPersist personPersist = new PersonPersist(this.connection);

If you made personPersist a field variable within the PersonPersistTestclass and moved this line out of each test method and into the setUp() method,code duplication in the class would decrease.

Write a custom Person comparison assertion

Another means of removing duplicated code from your tests is to avoid checking thefields of a retrieved person to match the expected values. Instead, a customassertion, such as the one shown in Listing 18, could be used.

Listing 18. A custom assertion

private void assertEquals(Person p1, Person p2) {assertEquals("IDs should match.", p1.getPersonId(),

p2.getPersonId());assertEquals("Names should match.", p1.getName(), p2.getName());assertEquals("Teams should match.", p1.getTeamName(),

p2.getTeamName());assertEquals("Addresses should match.", p1.getAddress(),

p2.getAddress());}

Then, within the test methods, you no longer have to explicitly check all four fields toverify correctness. This is particularly useful if more attributes are added to thePerson class at a later time, as it limits the number of code changes required.

Create an abstract superclass for your test cases

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 25 of 29

Page 26: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

As your application grows, it's inevitable that you want to persist more entities thanonly people. If you need to add a Team entity, you unit test it in a similar fashion.However, the createTable() and loadSqlFromFile() methods are commonbetween the PersonPeristTest and TeamPersistTest classes. If youintroduce an abstract superclass (AbstractPersistTest) for these entity testcases, it can contain these common behaviors.

Section 7. Summary

You should have a good understanding of the following useful test concepts:

• Using an abstract test case superclass to contain common behaviors

• Writing custom assertions that can be shared between tests

• Making effective use of test setup for any other test fixtures

• Ensuring good test coverage by testing exceptional cases

You should also have a better understanding of the various approaches to unittesting of persistence in a Java application. You learned how the embedded Derbydatabase can be an effective approach that allows repeatable, isolated, and durableunit tests. By using a standards-compliant database, the test environment is similarto your production servers without the overhead of administration and configuration.

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 26 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 27: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Downloads

Description Name Size Download method

Unit testing source code (Complete) complete.zip 2,172KB HTTP

Unit testing source code (Stage 1) stage1.zip 2,166KB HTTP

Unit testing source code (Stage 2) stage2.zip 2,168KB HTTP

Unit testing source code (Stage 3) stage3.zip 2,170KB HTTP

Information about download methods

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 27 of 29

Page 28: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

Resources

Learn

• Visit the developerWorks Apache Derby resource area for all your Derbyresources and articles.

• Read "The Cloudscape Detective" (developerWorks, December 2004), a tutorialintroducing you to Derby.

• Read "Develop Apache Derby applications in Eclipse" (developerWorks,January 2005).

• Check out "Controlling your test-environment with DbUnit and Anthill"(developerWorks, April 2004), an article covering approaches for databasetesting with DbUnit.

• Learn about the basics of Derby installation and usage in the online book,Getting Started with Derby .

• Explore the Derby manuals for in-depth knowledge of using, administering, anddeveloping for Derby. Be sure to check out the Tools and Utilities Guide sectionfor more information about the ij tool, including the full command reference.

• Refer to the Derby documentation on the Apache Web site for more informationon command-line tools.

• Visit the official Apache Derby Web site.

• Visit the developerWorks Open source zone for extensive how-to information,tools, and project updates to help you develop with open source technologiesand use them with IBM's products.

• Browse all the Apache articles and free Apache tutorials available in thedeveloperWorks Open source area.

Get products and technologies

• Download JUnit, the well-known unit testing framework for Java technology thatis well integrated into the Eclipse IDE.

• Download the latest release of IBM Cloudscape, a commercial release ofApache Derby.

• Get Eclipse plug-ins.

• Innovate your next open source development project with IBM trial software,available for download or on DVD.

Discuss

• Get involved in the developerWorks community by participating indeveloperWorks blogs.

developerWorks® ibm.com/developerWorks

Isolated unit testing of persistence with DerbyPage 28 of 29 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 29: Isolated unit testing of persistence with Derby - IBM · PDF fileIsolated unit testing of persistence with Derby ... Get help developing your database-driven unit-testing skills

About the author

Guy DavisGuy Davis is development team lead at Intervera Data Solutions, which providescomplete premium data-quality services and products for the energy sector. Guy hasworked on a variety of Java-based Web, desktop, and enterprise applications forboth government and industry. He has also contributed to open source projects,including the maintenance of a project management tool written in PHP. He isfinishing his master's degree in software engineering from the University of Calgarythis fall.

ibm.com/developerWorks developerWorks®

Isolated unit testing of persistence with Derby© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 29 of 29