Welcome!

IBM Cloud Authors: Zakia Bouachraoui, Elizabeth White, Yeshim Deniz, Pat Romanski, Liz McMillan

Related Topics: IBM Cloud

IBM Cloud: Article

Iterative Development with VisualAge for Java

Iterative Development with VisualAge for Java

Son, I tried once, and I failed. The moral of the story is: never try." - Homer Simpson

Iterative development is a process of growing software into existence. While the thought of software growing may sound a little strange, that's exactly what happens. As you iterate through the development process, more and more behavior is added to your application. With each new addition, your application gets closer and closer to a finished product. One of the really neat things about building an application iteratively is that it is almost always in a state where it can run. When iterative development is done right, you are never more than 15 minutes away from a demo. That's really powerful.

In the reality of today's software development requirements, iterative development is the only choice. Where development cycles were once measured in years, they are now measured in months. Where users were once measured in tens or hundreds, they are now measured in thousands and millions. By employing iterative development techniques you can bring software to market quickly (albeit incrementally). Additional functionality can be either scheduled or evolved to accomodate changes due to unanticipated requirements.

Iterative development is more than just an "analyze, design, implement, test" cycle repeated feature by feature. The stages of development are visited and revisited throughout the cycle. While implementing a single feature, you may have to revisit and modify a single method a number of times to adapt to evolving understanding. Iterative development requires discipline. Managed poorly, it is little more than hacking; the net result of which is very often a tangled mess of code that is going to cause endless grief and cost a lot of money over its lifetime. Iterative development involves change. As change is introduced into your application, things will break. By maintaining a comprehensive set of tests, you can quickly find and fix what's broken.

Iterative Development from the Top Down
A development iteration starts with a problem description; usually a short textual description. To demonstrate this process, we'll implement a simple object broker to handle storage and retrieval of BankAccount objects to and from a relational database.

An Object Broker is responsible for handling the life cycle of an object (an Enterprise JavaBean Home is an example of an object broker). In its simplest form, an object broker takes care of translating objects into rows in a relational database and rows from a relational database into objects. That is, the object broker abstracts the implementation of our persistence mechanism. Rather than dealing directly with Java Database Connectivity (JDBC) code, our application code interacts with JDBC indirectly through the object broker (see Figure 1).

Some understanding of what the object broker needs to do is required before starting the construction. Let's start with an example:

BankAccountBroker broker = new BankAccountBroker();
broker.insert(new BankAccount(42));

BankAccount account = broker.findByNumber(42);
account.credit(1000.0);
broker.update(account);

In this example, there are three things to test: inserting, finding, and updating bank accounts. With three things to test, at least three test cases are needed. More tests are probably required, but they can be addressed after the initial implementation. For example, the case where an attempt is made to insert a BankAccount that already exists should be considered.

Before starting to write code, some Java packages (and a VisualAge for Java Project) must be defined. The object broker is going to involve domain model objects (BankAccounts), object brokers, and tests for the object brokers. Appropriate packages are defined accordingly:

_ com.wtb.models;
_ com.wtb.brokers;
_ com.wtb.brokers.tests;

As a matter of practice, test code should be in a separate package from the code being tested. Classes have special access to the protected members (fields or methods marked as protected) of classes defined in the same package. If test classes were to be defined in the same package as the classes being tested, then the accessibility of the classes might not be effectively tested. More pragmatically, tests are useful for development, but are not so useful for deployment. By keeping the tests in a completely different package, deployment becomes a simple matter of excluding the test package.

JUnit (www.junit.org) is a useful tool for developing tests. In JUnit, the test cases are written in Java and are subject to the same version control as the code being tested. By keeping the tests in the same VisualAge project, you are virtually guaranteed that everywhere the code goes the tests will follow. At the same time, if the tests are maintained along with the code, the VisualAge version control can guarantee that the code and the tests are always synchronized.

The basic building block in JUnit is the TestCase class. All tests are derived from this class, including the BankAccountTests class, which contains code to test the BankAccountBroker:

package com.wtb.brokers.tests;

import junit.framework.*;
import com.wtb.brokers.*;

public class BankAccountBrokerTests extends TestCase {
public BankAccountBrokerTests(String name) {
super(name);
}
public void testFind() {}
public void testInsert() {}
public void testUpdate() {}
}
By default, in a JUnit TestCase all methods prefixed by "test" are considered individual test cases and will be run automatically by the test runner. The test methods are not invoked in any particular order and are not expected to depend on other tests (that is, you should not generally define tests that depend on the results of other tests; each test is expected to clean up after itself). As they currently exist, these tests are not particularly interesting, the actual test code has not been filled in yet. In fact, the code being tested hasn't even been defined yet. The remaining steps are an exercise in filling in these methods and making them run. Once these methods have been created, and the tests run successfully, this part of the iterative development process is complete.

The easiest way to test to see if an insert works is to see if a row has been added to the database. For that, a little JDBC code will be required in the test method:

public void testInsert() {
// set up the objects we need for the test BankAccount account = new
BankAccount(42);account.credit(1000.0);
// actually do the insert
broker.insert(account);

// Evaluate the results. If the insert
// works, then we should find a row
// in the BANKACCT table with the
// corresponding values.
ResultSet results = statement.executeQuery
("Select ID, BAL from BANKACCT where
ID=42");
if (!results.next()) fail("Account number
42 not found!");
assertEquals(42, results.getInt(1));
assertEquals(1000.0, results.getDouble(2), 0.0);
}

The last chunk of code in this method verifies the result of the test. When the testInsert() method runs successfully, a row with values corresponding to the data in the inserted BankAccount should exist in the database. If no such row is found, then the test simply fails and a message is reported. If a row is found, its contents are inspected to ensure that they contain the expected values.

The testInsert() method makes use of a number of classes and methods that do not exist yet, which will cause the compiler to warn of a lot of problems (see Figure 2). Some of these problems can be worked out immediately. For example, the use of the ResultSet class will be flagged as an error that can be resolved by importing the java.sql package. Most of the other problems can only be solved by writing more code. As it is, this method can be saved, but will appear with a red "X" beside its name to indicate that a problem exists (see Figure 3).

Many developers see the red X as something to be avoided at any cost (after all, it represents a problem in the code). This can lead to a bottom-up approach when writing software. It is better to think of the Xs as placeholders indicating what has to be done next. There is a lot of benefit to top-down development (as is demonstrated here); in particular, you build what you actually need, not what you think you need.

A nice feature of VisualAge for Java is the "All Problems" page (see Figure 4) in the Workbench. As might be expected, this page puts all code problems in one place for easy identification and editing. This page shows both errors and warnings (use of deprecated methods, for example), which makes finding and solving problems relatively easy.

VisualAge for Java's browser-centric paradigm is very helpful for iterative development. The testInsert() method can be opened in its own window and left open while the necessary pieces are built (keeping it handy for reference). In fact, any number of windows can be opened on arbitrary parts of the code. We might, for example, have one window open on the testInsert() method, and another open on the entire BankAccountBrokerTests class. Each window will update automatically to reflect changes in the other.

The immediate task is to make the testInsert() method work. Starting from the top of the method, it is clear that the com.wtb.models.BankAccount class with a one-argument constructor and a credit(double) method is needed. This is left as an exercise for the reader (don't forget to build tests).

Next comes the broker field. The BankAccountBrokerTest class is modified to include a field named "broker" with type com.wtb.brokers.BankAccountBroker. The field is initialized using the setUp() method:

public void setUp() {
broker = new BankAccountBroker();
}

The setUp() method is called before each test method is run. A corresponding tearDown() method is invoked after each test. With the broker field populated, attention is focused on the insert(BankAccount) method for the BankAccountBroker class. Of course, the BankAccountBroker class does not exist yet and must be defined:

public class BankAccountBroker {
}

The insert(BankAccount) method is required by the BankAccountBroker class to convert the object into something that can be stored in the database.

public void insert(BankAccount account) throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
try {
connection = getConnection();
statement = connection.prepare
Statement("insert into BANKACCT (ID,
BAL) values (?, ?)");
statement.setInt(1, account.get
Number());
statement.setDouble(2, account.get
Balance());
statement.execute();
} catch (SQLException e) {
throw e;
} finally {
close(statement);
close(connection);
}
}

This method uses a standard JDBC PreparedStatement to insert values into the BANKACCT table. As this method was being constructed, it became clear that the SQLException, which is thrown by every JDBC method, must be handled. This method's answer to an SQLException is to clean up the resources it used and pass the exception on to the caller. At a later time, alternative exception handling might be explored, but for now, passing the SQLException gets the job done.

The insert(BankAccount) method calls a few methods that don't exist. The getConnection() method, as the name implies, gets a database connection:

private Connection getConnection() throws SQLException {
DriverManager.registerDriver(new jdbc.idbDriver());
return DriverManager.getConnection("jdbc:idb:c:/idb/test/ns.prp");
}

The getConnection() method really should use a DataSource to get a connection (at the very least, the JDBC driver and URL should not be hard-coded). DataSources provide connection pooling, but also require quite a lot of additional coding to make it work. Using the DriverManager from classic JDBC makes the test work without adding a lot of complexity. When this type of compromise is made, it is important to make a note to come back to this method and fix it.

With the getConnection() method in place, attention turns to the two "close" methods at the end of the insert(BankAccount) method. These methods are very simple in purpose and design. When closing a statement or connection, SQLException must be handled. Aesthetically speaking, handling these exceptions directly will make the code bulkier and harder to read; besides, exposing the exception handling code will not add any value. The close(Statement) method is shown below (the corresponding close(Connection) method is virtually identical):

private void close(Statement statement) {
if (statement == null) return;
try {
statement.close();
} catch (SQLException e) {
// Ignore and move on.
}
}

Note that the ResultSet is not explicitly closed; according to the JDBC specification (www.javasoft.com/jdbc), ResultSets automatically close when the statement is closed.

At this point, the BankAccountBroker class appears to be complete (though we won't know for certain until it passes our tests). However, the testInsert() method in the BankAccountTests class still has errors that must be resolved. This is how this method currently appears:

public void testInsert() {
// setup the objects we need for the test
BankAccount account = new BankAccount(42);
account.credit(1000.0);

// actually do the insert
broker.insert(account);

// Evaluate the results. If the insert
// works, then we should find a row
// in the BANKACCT table with the
// corresponding values.
ResultSet results = statement.executeQuery
("Select ID, BAL from BANKACCT where
ID=42");
if (!results.next()) fail("Account number
42 not found!");
assertEquals(42, results.getInt(1));
assertEquals(1000.0, results.getDouble(2), 0.0);
}

With the testInsert() method highlighted, VisualAge displays the problems with the method in the status bar at the bottom of the window (see Figure 5). The status bar indicates that the statement field has not been declared. Remember that the purpose of the last chunk of code in this method is to ensure that the insert actually worked by checking the database to see if a corresponding row has been added. Given this understanding, the last chunk of code requires a statement from a connection to the same database employed by the BankAccountBroker class.

The statement and connection fields are added to the test case as fields of type java.sql.Statement and java.sql.Connection respectively. The setUp() and tearDown() methods are defined accordingly:

public void setUp() throws SQLException {
broker = new BankAccountBroker();

DriverManager.registerDriver(new
jdbc.idbDriver());
connection = DriverManager.getConnection
("jdbc:idb:c:/idb/test/ns.prp");
statement = connection.createStatement();
}

public void tearDown() throws SQLException {
statement.close();
connection.close();
}

With the new fields defined, you might expect that the job is done. However, VisualAge insists on continuing to mark the testInsert() method with an X. With a little help from the status bar, it seems that the testInsert() method needs to handle a number of exceptions. This is an easy fix: Exception is included in the throws clause for the method. JUnit will catch the exception and report it if it is thrown.

The JUnit user interface (see Figure 6) is opened from the Scrapbook by executing junit.swingui.TestRunner.main(new String[] {}). To run the tests, the full name of the test class (com.wtb.brokers.tests.BankAccountBrokerTests) is entered into the entry field at the top of the window and the "Run" button is pressed (note that the "..." button, which browses the file system for Java classes, does not work in VisualAge).

The red bar that stretches across the windows indicates that at least one test failed. The numbers (just below the bar) indicate that three tests have run and one error occurred. The test method containing the error is indicated in the list below. Recall that three test methods were built (testInsert(), testUpdate() and testFind()), but only one of these test methods actually does anything.

At the bottom of the JUnit window, the stack trace for the highlighted method is shown. According to the stack trace, the table has not been defined in the database. The table is defined and the tests are rerun. This time, all the tests pass. However, when we run the tests a second time we have a problem: the second time the testInsert() method is run, the row already exists (it was inserted by the first test). For an automated test to be truly useful, it must be repeatable. To be repeatable, the testInsert() method must undo the insert after it is complete.

Unit testing always gets messy when external entities are involved. In this case, the object broker depends on a particular database manager being installed on our system. Further, that database manager must be set up with an appropriate database and table for the tests to work. Unit testing purists might suggest that these are not unit tests at all due to the dependence on manually-configured external entities - and they might be right.

Cleanup can be accomplished by recreating the database for each test. This is a bit of a sledgehammer approach, but it guarantees that the database is in a pristine state for each test. Alternatively, each test can simply delete the rows it creates at the end of the test. For now, the tests and tables are so simple that the simplest thing that could possibly work is to recreate the tables with each test. When this becomes too difficult, alternatives will be explored. To do this, the setUp() method in BankAccountBrokerTests is updated to drop and then re-create the table:

public void setUp() throws SQLException {
broker = new BankAccountBroker();

DriverManager.registerDriver(new jdbc.idbDriver());
connection = DriverManager.getConnection("jdbc:idb:c:/
idb/test/ns.prp");
statement = connection.createStatement();

statement.execute("drop table BANKACCT");
statement.execute("create table BANKACCT (ID integer
primary key, BAL numeric(7,2))");
}

Conclusion
The process demonstrated here takes about 10 minutes out of the day of a reasonably productive VisualAge for Java developer. The code is not perfect. In fact, it is flawed in a number of ways. However, it does provide the functionality that is required. Further, that functionality has been implemented in the least complex way possible. As the requirements are better understood, the code will be evolved. Over time, the code will grow in sophistication and complexity. With care, the complexity can be managed.

VisualAge for Java provides unparalleled support for iterative development. VisualAge's browser-based code-editing paradigm permits code to be viewed and edited as projects, packages, types, or methods. With multiple windows, a developer can effectively edit many parts of the application simultaneously, an absolute necessity for iterative development. The "All Problems" page in the Workbench can be used to find and resolve code problems quickly.

The iterative development process is more than just coding a feature and testing it. Each iteration in the process is itself composed of many fine-grained iterations. By making these iterations, tests are run more frequently so small problems are found before they become big problems and the process runs more efficiently. Very often tests are the first things to get cut when time gets tight and this is a big mistake. Without effective tests in place, iterative development can quickly degenerate into a coding nightmare.

Iterative development works well from the top down. By writing the test case first and then the code, you set the stage in advance for what needs to be built. When building from the bottom up, there is a tendency to build too much, or to build the wrong things. Top-down development, with a little help from JUnit and the little red Xs, builds exactly what you need.

This month, I've tried to provide a glimpse into the iterative development process. In future articles, we'll evolve the object broker (and figure out where it fits in the architecture) with a little help from the VisualAge debugger.

References
1.   Fowler, M.(1999). Refactoring: Improving the Design of Existing Code. Addison-Wesley.
2.   Davis, M. "Incremental Development with Ant and JUnit, VisualAge Developer Domain," www.106.ibm.com/developerworks/library/j-ant/?n-j-11160

More Stories By Wayne Beaton

Wayne Beaton is chief evangelist at Eclipse.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


IoT & Smart Cities Stories
Moroccanoil®, the global leader in oil-infused beauty, is thrilled to announce the NEW Moroccanoil Color Depositing Masks, a collection of dual-benefit hair masks that deposit pure pigments while providing the treatment benefits of a deep conditioning mask. The collection consists of seven curated shades for commitment-free, beautifully-colored hair that looks and feels healthy.
The textured-hair category is inarguably the hottest in the haircare space today. This has been driven by the proliferation of founder brands started by curly and coily consumers and savvy consumers who increasingly want products specifically for their texture type. This trend is underscored by the latest insights from NaturallyCurly's 2018 TextureTrends report, released today. According to the 2018 TextureTrends Report, more than 80 percent of women with curly and coily hair say they purcha...
The textured-hair category is inarguably the hottest in the haircare space today. This has been driven by the proliferation of founder brands started by curly and coily consumers and savvy consumers who increasingly want products specifically for their texture type. This trend is underscored by the latest insights from NaturallyCurly's 2018 TextureTrends report, released today. According to the 2018 TextureTrends Report, more than 80 percent of women with curly and coily hair say they purcha...
We all love the many benefits of natural plant oils, used as a deap treatment before shampooing, at home or at the beach, but is there an all-in-one solution for everyday intensive nutrition and modern styling?I am passionate about the benefits of natural extracts with tried-and-tested results, which I have used to develop my own brand (lemon for its acid ph, wheat germ for its fortifying action…). I wanted a product which combined caring and styling effects, and which could be used after shampo...
The platform combines the strengths of Singtel's extensive, intelligent network capabilities with Microsoft's cloud expertise to create a unique solution that sets new standards for IoT applications," said Mr Diomedes Kastanis, Head of IoT at Singtel. "Our solution provides speed, transparency and flexibility, paving the way for a more pervasive use of IoT to accelerate enterprises' digitalisation efforts. AI-powered intelligent connectivity over Microsoft Azure will be the fastest connected pat...
There are many examples of disruption in consumer space – Uber disrupting the cab industry, Airbnb disrupting the hospitality industry and so on; but have you wondered who is disrupting support and operations? AISERA helps make businesses and customers successful by offering consumer-like user experience for support and operations. We have built the world’s first AI-driven IT / HR / Cloud / Customer Support and Operations solution.
Codete accelerates their clients growth through technological expertise and experience. Codite team works with organizations to meet the challenges that digitalization presents. Their clients include digital start-ups as well as established enterprises in the IT industry. To stay competitive in a highly innovative IT industry, strong R&D departments and bold spin-off initiatives is a must. Codete Data Science and Software Architects teams help corporate clients to stay up to date with the mod...
At CloudEXPO Silicon Valley, June 24-26, 2019, Digital Transformation (DX) is a major focus with expanded DevOpsSUMMIT and FinTechEXPO programs within the DXWorldEXPO agenda. Successful transformation requires a laser focus on being data-driven and on using all the tools available that enable transformation if they plan to survive over the long term. A total of 88% of Fortune 500 companies from a generation ago are now out of business. Only 12% still survive. Similar percentages are found throug...
Druva is the global leader in Cloud Data Protection and Management, delivering the industry's first data management-as-a-service solution that aggregates data from endpoints, servers and cloud applications and leverages the public cloud to offer a single pane of glass to enable data protection, governance and intelligence-dramatically increasing the availability and visibility of business critical information, while reducing the risk, cost and complexity of managing and protecting it. Druva's...
BMC has unmatched experience in IT management, supporting 92 of the Forbes Global 100, and earning recognition as an ITSM Gartner Magic Quadrant Leader for five years running. Our solutions offer speed, agility, and efficiency to tackle business challenges in the areas of service management, automation, operations, and the mainframe.