Saturday, December 29, 2007

Test Driven Development and GUI Testing: Unit tests

Previous article:Test Driven Development and GUI Testing: Temperature Converter details

We now have an idea of the application requirements as we defined our Use Case: Convert Temperature.
Using Test Driven Development we will implement this use case. We are also using UML to maintain our model.

Main project

Using Netbeans 6.0
  1. Create a Java project (File | New Project... | Java | Java Application)
  2. Name the project something like TDD
  3. Select the desired location
  4. Uncheck Create Main Class
  5. Finish

UML Project

  1. Create a new UML project (File | New Project... | UML | Java Platform model)
  2. Name it something like TDD-UML
  3. Choose previous location. If the previous project folder was TDD select this project folder
  4. Finish

Create Class Diagram

  1. Create a Class Diagram and name it TDD Class Diagram
  2. Create a package tdd dragging a package from the palette
  3. Create a TemperatureConverter class
  4. Create a Nested Link from TemperatureConverter to tdd package
Now, you should have a diagram like this


Generate code

  1. Select the tdd package
  2. Right click and select Generate Code...
  3. Select the previously created project, TDD as Target Project
  4. Select Source Packages as Source Root
  5. Press OK
We now have the structure of an empty class inside the tdd package

Add testing infrastructure using XTest

  1. Select TDD project node in Projects
  2. Right click and select New | Other...
  3. Select Testing Tools and then XTest infrastructure (if it's not there go to the plugin manager and install it)
  4. Finish

Generate test cases

  1. Right click on the TemperatureConverter class node in the TDD project
  2. Select Tools | Create JUnit Test...
  3. IMPORTANT: Select JUnit 3.x
  4. Accept the name tdd.TemperatureConverterTest for the class
  5. Select Location: Unit Test Packages
  6. OK
Now we will add our tests, even though our TemperatureConverter class is not clearly defined yet. Adding those tests will help us define our TemperatureConverter class and it's the essence of Test Driven Development.

Adding the test class to the UML model

  1. Right click on the editor window of the TemperatureConverterTest class
  2. Select Reverse Engineer...
  3. Select Use Existing UML Project and Target Project as TDD-UML
  4. OK

Complete the TDD Class Diagram

  1. Select TDD Class Diagram in the editor
  2. Drag and drop the TemperatureConverterTest class, which is inside the tdd package node, from the Model (TDD-UML | Model | tdd) to the diagram. Sometimes it's also needed to drag and drop the tdd package.
  3. You may want to change the color of the class to recognize it visually as a test, right click on it and select Class | Background color, we use green in this example.

Define the tests

Accordingly to our use case in TemperatureConverter we can define two tests
  • testCelsiusToFahrenheit, to test our converter
  • testInvalidCelsiusTemperature, to validate that wrong temperatures are detected
As a method to verify our converter we will use some results obtained from an external converter source and we will build a table with some representative values.
An online converter can be found at http://www.onlineconversion.com/temperature.htm.
Let's add these components to get our tests finished.

Add the tests methods

In TDD Class Diagram
  1. Right click on the TemperatureConverterTest Operations compartment and select Insert Operation... and insert the operations mentioned previously
  2. add testCelsiusToFahrenheit method
  3. add testInvalidCelsiusTemperature method

Add conversion table

In TDD Class Diagram
  1. Right click on the TemperatureConverterTest Attributes compartment and add attribute conversionTable, set its type to Map<Integer, Integer> and be sure to check the static and final properties
  2. Set the default value to new HashMap<Integer, Integer>()
We should have now this diagram

Generate the code

  1. Select the TemperatureConverterTest class only (be sure that no other component is selected), right click and Generate Code...
  2. Be sure to select Source Root: Unit Test packages
  3. OK

Initialization code

  1. Right click on the ConversionTable attribute and select Navigate to Source...
  2. If some imports are missing just press Shift+Ctrl+I
  3. Initialize the conversion table to this code, just after its definition

    static {

    // initialize (c, f) pairs

    conversionTable.put(0, 32);

    conversionTable.put(100, 212);

    conversionTable.put(-1, 30);

    conversionTable.put(-100, -148);

    conversionTable.put(32, 90);

    conversionTable.put(-40, -40);

    conversionTable.put(-273, -459);

    }

  4. Add this method (the prefix test is important for JUnit 3.x)

    public void testCelsiusToFahrenheit () {

    for (int c: conversionTable.keySet()) {

    int f = conversionTable.get(c);

    String msg = "" + c + "C -> " + f + "F";

    assertEquals(msg, f, TemperatureConverter.celsiusToFahrenheit(c));

    }

    }


  5. Add this method (the prefix test is important for JUnit 3.x)

    public void invalidCelsiusTemperature () {

    try {

    int f = TemperatureConverter.celsiusToFahrenheit(-274);

    } catch (RuntimeException ex) {

    if (ex.getMessage().contains("below absolute zero")) {

    return;

    }

    else {

    fail("Undetected temperature below absolute zero: " + ex.getMessage());

    }

    }


    fail("Undetected temperature below absolute zero: no exception generated");

    }

  6. If some imports are missing just press Shift+Ctrl+I

Create the real code

We have now the tests for code that still doesn't exist. If everything was fine, you should see a light bulb in the line where TemperatureConverter.celsiusToFahrenheit is invoked and gives you the to Create Method celsiusToFahrenheit(int) in tdd.TemperatureConverter
But, sometimes things use to fail, and if it's not appearing use this work around:
  1. Double click on the TemperatureConverter class node, and go to the editor, add an empty line, delete it, just to force the change, then save the file.
  2. Is this a Netbeans bug ?
Now Create Method celsiusToFahrenheit(int) in tdd.TemperatureConverter should have appeared, select it and create the method.

Running the tests

Our code compiles now, and we are in the condition of running the tests. After all, we are using Test Driven Development, don't forget it.
Go to the TDD project node and
  1. Right click and select XTest | Run unit Tests...
These tests will fail, because we haven't already implemented the celsiusToFahrenheit conversion method.
We obtain something like this



We can see that one test failed because celsiusToFahrenheit converter was not implemented and the other because this method didn't generate the expected exception on an invalid temperature.

So, what's left is to go there an implement it !

Implement celsiusToFahrenheit

We need to replace the default implementation of this method

throw new UnsupportedOperationException("Not yet implemented");


with

return (int)Math.round(celsius * 1.8 + 32);


Remember to change the parameter name from i to celsius.

Run the tests again

Running the tests again we will see how one test succeed but the other fails with the message

Undetected temperature below absolute zero: no exception generated

That's because we are expecting an exception to be thrown if the temperature is below the absolute zero but our first attempt doesn't do that.

Let's add this condition, but we first need the ABSOLUTE_ZERO_C constant.

  1. Synchronize the code with the UML by right clicking on the TemperatureConverter editor and then Reverse Engineeer...
  2. Select Use Existing UML PRoject and set it to TDD.
  3. Then OK and Yes to all.

Add ABSOLUTE_ZERO_C constant

This is the absolute zero temperature in Celsius. Now, our code and model are synchronized.

Go to the TDD Class Diagram.

Right click on the TemperatureConverter Attributes compartment in the class diagram

  1. Add ABSOLUTE_ZERO_C, with type int, default value set to -273, and check static and final in properties
  2. Select the TemperatureConverter class and right clicking select Generate Code..., be sure to select Source Root: Source pckages
  3. Select Add mergers to existing source
  4. OK


Now

  1. Select the celsiusToFahrenheit method and then right clicking Navigate to Source...

Modify celsiusToFahrenheit

Add the absolute zero check

if (celsius < ABSOL?UTE_ZERO_C) {

throw new RuntimeException("Invalid temperature: " + celsius + " below absolute zero");

}

return (int)Math.round(celsius * 1.8 + 32);





For some reason (Netbeans bug ?) conversionTable disappear from the Class Diagram and from the model, but it's still there because it's not possible to add another attribute with the same name and if code is generated the attribute is still there.

Run the tests one more time

And both tests pass


Next article

Next article Test Driven Development and GUI Testing: Functional tests will show how to build the swing based GUI using Test Driven Development approach and we will obtain our working and fully tested application.

No comments: