Turnleaf Design Ramblings of a junior developer

26Oct/093

An Intro into Test Driven Development with JUnit4

Please read the technical guide before starting this tutorial.

This article will mark the first of a long-term series covering professional software development. For the lowdown on this project check out this article. Be sure to give me your feedback as it will be vital in helping me develop better tutorials in the future.

Test driven development seemed like a natural choice as a lead off to my series of tutorials as I had to explain why I am writing all these tests. It is also a very good development methodology that will actually save a lot of time by reducing the amount of time spent debugging. For this tutorial and the entire project, I will be using Junit4. For a synopsis on test driven development you can check out the wikipedia article here. If you need a brief refresher on JUnit you can read my tutorial here (written in Junit3).

The goal of this tutorial, from a project stand point, is to get the connection to our (MySQL) database working. To do this we are going to need several things: a MySQL server, a client to access the server, and a JDBC driver to allow our application to access the server.

The MySQL server:
I will be using MySQL server 5.0 for this project, you can download it here. The tutorial instructions will be based upon the user being “root,” the password “turnleaf,” and the url “http://localhost:3306.”

The MySQL client:
I will be using HeidiSQL to query and manipulate the database. Once the client is installed import this sql file to setup the database and table.

The JDBC Driver:
You can download the JDBC driver here. Please include it on your projects build path.

To begin this project, please checkout revision 7 from the code repository.

Once you have downloaded the project go ahead and navigate around it a little bit. You will notice I already have several source files; Forecast, ForecastDao, and ForecastDaoImpl. Forecast is a bean for holding all weather information for a specific date (this bean will likely be modified in the future). We also have ForecastDao which is an interface and ForecastDaoImpl which implements ForecastDao. I plan on covering interfaces in more detail in the future, but if you are unfamiliar with the concept you can read a brief description here.

Update to revision 8 and you will see I added in my first unit test. The unit test is checking to see if I get any returns when I run getAllForecasts(). Here is the code:

@Test

public void testGetAllForecasts(){

List<Forecast> forecasts = dao.getAllForecasts();

Assert.assertTrue(!forecasts.isEmpty());

}

If you attempt to run the test you should get a null point error when you attempt to check if forecasts is empty. If you look at the implementation of getAllForecasts() in ForecastDaoImpl it is obvious why, getAllForecasts() is returning null. This is one of the tenants of TDD, write fail first tests.

Go ahead and update to revision 9. You will see I have added connection information to getAllForecasts(). Don't worry about it being messy we will refactor it later. Go ahead and run the unit test again. You will still fail, but in the console shows we are successfully connecting to the database.

Update to revision 10. I have made several more changes, most noticeably if you run the test it should now pass! (If not check to make sure your database is setup correctly) Before we start celebrating too much, lets actually check the contents of the list to make sure we are getting the right data. Update to revision 11, I have added a few more checks, and we are in fact getting the correct data, awesome!

Author note: For some dumb reason when you create a new Junit4 test class in Eclipse it does not automatically inherit TestClass. Anyways I inherit that class now so you can just do assertEquals(expected, actual) instead of Assert.assertEquals(expected, actual).
If you checked the implementation of getAllForecasts() before updating to revision 10, you will noticed I am only setting the date value of my forecast bean. My new tests initially failed (I originally thought I was setting all fields), thus the importance of not only writing unit tests, but writing good unit tests.

So now it is time to start refactoring. With our unit test we now have a good baseline of how the system should behave. This way when we are making changes we can be confident we are not breaking the system because we will be getting the same output for the same input.

Update to revision 13. You will see that I have created the getConnection() method and that contains the database connection logic.

private Connection getConnection() {

Connection conn;

try {

String userName = "root";

String password = "turnleaf";

String url = "jdbc:mysql://localhost/weather";

Class.forName("com.mysql.jdbc.Driver").newInstance();

conn = (Connection) DriverManager.getConnection(url, userName,

password);

} catch (Exception e) {

throw new DataAccessException(e);

}

return conn;

}

I pretty much just copied and pasted (one of the VERY few times copying and pasting is ok) the connection logic into this method. The most noticeable change I made is in the catch clause. There is a bunch of different exceptions that could be thrown when attempting to connect to the database, sine they all end in the same scenario, unable to connect to the database, I kept the same generic catch(Exception). However you should never just throw exception so I created my own custom exception called DataAccessException. Inside the super(Throwable) constructor I set it up to add a message stating “Unable to connect to data source,” I also have it extend RunTimeException turning it into an unchecked exception.

***Philosophical warning***
This is philosophical decision. As projects become more complex it can become difficult to catch and/or throw an exception at every level. However, you should catch the exception at some point to give the user a reasonable error message and let you know when the application is throwing exceptions. There is another school of thought that all exceptions should be checked as it lets a developer know what exceptions and method could throw, and other various reasons.

Between the message and the name of the exception, it should be pretty clear that when this exception is thrown it means the application failed to connect to the database. Running the unit test we will see everything is still passing so we can be confidant that the refactoring did not break the system.

Update to revision 14 and you will see I refactored out the closing of the connection. Exceptions should never be ignored, personally I would say being unable to close a database connection could be a major issue. So instead of catching Exception I changed it to only catch SQLException and throw the DataAccessException except I will have my own message in their stating the connection could not be closed. Here is what the closeConnection() method looks like:

private void closeConnection(Connection conn) {

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

throw new DataAccessException("Could not close connection", e);

}

}

}

From what it originally looked like, the getAllForecast() method is starting to look a lot better as well as the ForecastDaoImpl class in general. Adding new methods that retrieve data from the database will be easier as all I have to do to get a connection is call getConnection() and to close it closeConnection(Connection). On top of that if I need to change my database connection information I only have to do it in one area.

Despite this refactoring, this is hardly an ideal data access layer. With the test unit already created go ahead and continue to refactor getAllForecast(), a good place to start would be how I create a new Forecast object. I would also suggest adding some new functionality yourself a couple of examples might be; getForecastByDate(Date) or getForecastByCondition(String condition) (Remember create unit tests!). I will continue to refactor and update this work myself. So you can run updates and compare your work to my own.

Addendum and thanks:
Because I am an idiot I couldn't remember how to write a JDBC connection. My initial connection method is predominantly based off of the code in this article: http://www.kitebird.com/articles/jdbc.html
For being able to go to a specific revision with a url:
http://www.perhammer.com/2008/07/subversion-in-url-revision-browsing.html

Bookmark and Share

Technorati Tags: , , ,

Related posts:

  1. A brief intro into unit testing with JUnit
  2. Ramblings: Should unit tests talk to a data source?
  3. 6 Steps for fixing any bug
  4. Mocking JDBC Connections with MockRunner
  5. 8 Signs your code sucks
Comments (3) Trackbacks (2)
  1. “For some dumb reason when you create a new Junit4 test class in Eclipse it does not automatically inherit TestClass. Anyways I inherit that class now so you can just do assertEquals(expected, actual) instead of Assert.assertEquals(expected, actual).”

    If you’re using Java 1.5 or later (and why not? Generics are extremely useful) then why not just use the static imports? Go to the Eclipse preferences then Java > Editor > Content Assist > Favorites and add “junit.framework.Assert.*”, then make sure “Use static imports” is checked in Java > Editor > Content Assist and Eclipse will auto-complete “assertEquals” for you and include the static import.

    Overall it looks like an interesting start to an article, but I’d probably have started TDD with something a little simpler than trying to do database connections (especially since unit tests that have a database backend are best not connecting to a database for consistency reasons – although the connector itself may be a different matter).

  2. Well, the @Test annotation tells you it’s a test.
    Extending TestCase, in addition, seems redundant :-)

    Give a little reading on “Composition over inheritance”.

    A minor criticism, when possible try to avoid the not (!) operator in your tests.
    For example, instead of:
    Assert.assertTrue(!forecasts.isEmpty());
    do:
    Assert.assertFalse(forecasts.isEmpty());

    Later, when folks are reading the code, it’s really easy to miss those.


Leave a comment