Data Input / Output and JUnit
[Practical 8 of 11 - Part 1]


Creating the Class Structure:

Create a new project in Netbeans called FileInputOutput of a type Java Class Library. Right click on the Source Packages select New and from the drop down list Java Package.... Give the new package a suitable name, since we are studying this course at Leeds University lets use our web address as the prefix, so the full package name should be uk.ac.leeds.filereader.

Right click on the new package and select New and this time select Java Class... from the drop down list. Call the new class CSV.

We are going to make our new class abstract because we don't want anyone to try and create an object from it on its own. We are also going to extend this class to provide functionality for both loading and saving files.

Before the keyword class insert the keyword abstract as shown in Figure 1.

New absract CSV class.
Figure 1

Next we are going to create two more classes extending from CSV called CSVReader and CSVWriter. Create the two classes by right clicking on the filereader package and selecting to create a new class twice giving each class the appropriate name. Now we edit the class declaration to make the two new classes extend from CSV.

First the CSVReader class. Edit the class declaration appending the keyword extends and the name of the CSV class as shown in Figure 2.
New CSVReader class extends CSV.
Figure 2
Next do the same to the CSVWriter class as shown in Figure 3...
New CSVWriter class extends CSV.
Figure 3

Now we need to think about the methods that we will need.

For the CSVReader we will need a method to load a file and one to parse each line of the file into the different fields of data. We will also need a method to access the data.

For the CSVWriter we will need a method to save the data and a method to construct the lines to be saved.

So lets create some empty methods in our classes.

First the CSVReader. Create three methods using the lines:

  • public void loadData(File file){}
  • public String[] parseLine(String line){}
  • public String[][] getData(){}

Your class should look something like Figure 4.

CSVReader with method structure.
Figure 4

The two compile errors we see on the parseLine and getData methods are because they have a return value but are not yet returning anything. This is to be expected.

We can temporarily return empty values to remove the errors. Enter the line return new String[]{""}; into the parseLine method and the line return new String[][]{{""}}; into the getData method.

The compile error on the loadData method is because we don't have the associated import for the File object type that is the parameter to the method. To resolve this error you can left click on the red blob on the light-bulb in the left hand margin you should get the option to Add import for java.io.File, left click this option and the import is added to the top of the class.

Your class should now look like the one in Figure 5.

CSVReader with method structure and no compile errors.
Figure 5

Next create two methods in the CSVWriter using the lines:

  • public boolean saveData(String fullFileName, String[][] data){}
  • public String constructLine(String[] data){}

Your class should now look like the one in Figure 6.

CSVWriter with method structure.
Figure 6

The two compile errors are because we have not inserted any return types. To temporarily resolve this issue insert the line return false; into the saveData method and the line return ""; into the constructLine method.

Your class should now look like the one in Figure 7.

CSVWriter with method structure with no compile errors.
Figure 7

Designing Test Conditions:

To design the test conditions that our classes need to adhere to we need to think about what we want them to do and perhaps more importantly what we don't want them to do. Lets look at the CSVReader first.

We want it to read the data into a two dimensional array of String[][]. The first dimension is the rows and the second the columns. This is our first test condition.

We want the fields to be separated by commas, but sometimes we have a csv file that is delimited by a different character. So we would like to have fields parsed by a specified character. Condtion number 2.

When an empty field is encountered return an empty string in the corresponding location. This should be true whether the empty field starts or terminates the line.

If the number of fields for any record in the file is different report an error to the user.

OK now lets think about the CSVWriter.

We want it to save a file to the specified location and report an error if the file already exists.

The file should be complete and structured one row per line.

The correct delimiter should be used to construct the field boundaries.

This gives us four test conditions for each class, summary below.

CSVReader - Test Conditions Summary

  1. Return a 2D array. Dimension 1 = rows, dimension 2 = columns.
  2. Parse fields by specified character with no leading or trailing white space.
  3. Insert an empty string when an empty field is encountered whether at the beginning or end of line.
  4. Report an error if the number of fields is not consistent throughout the file.

CSVWriter - Test Conditions Summary

  1. Save file to correct location.
  2. Report an error if the file already exists.
  3. Check that all of the data is being saved, line by line.
  4. The construction of the fields should use the correct specified delimiter.

Now we need to write the tests to implement the test conditions. However, because of time constraints we will only implement the tests for the CSVReader in this practical. Have a go at designing some tests for the CSVWriter by yourself.


Implementing the Tests:

To create the tests we first need a test class. To create this right click on the CSVReader file in the project browser and select Tools and then Create Tests.

A screen similar to the one in Figure 8 will appear, leave all of the default settings and press OK. Explore the settings using the help menu at your leisure, the inbuilt help in Netbeans is very good, and there is plenty of online support.

Test class creation wizard 1.
Figure 8

The next screen will ask you which version of Junit to use. Select version 4.x as shown in Figure 9.

Test class creation wizard 2.
Figure 9

Expanding the Test Packages folder in the project explorer should reveal a package with the same name as your package in the Source Packages folder, uk.ac.leeds.filereader. Expanding the package will show a class has been automatically generated for you called CSVReaderTest. See Figure 10.

Test class in the test packages area.
Figure 10

Open up the test class CSVReaderTest. Scroll through the class, you will see a great deal of automatically generated code. The three things we want to concentrate on are the three methods with the notation @Test above them. These are the three tests that have been automatically generated.

What we need to do now is adjust these auto-generated tests to implement the four test conditions we outlined above.

Test 1 - 2D array, dimension 1 = rows, dimension 2 = columns.

Find the testGetData method. It will look like the one in Figure 11.
Auto-generated testGetData method
Figure 11

Adjust the code in the testGetData method to reflect that in Figure 12.

Download the test_1.csv test file and save it locally on your computer by right clicking the link and selecting Save Linked File As.... Adjust the line starting at 82 to point to the location of the file on your computer. Windows users will need to add the drive identifier at the start of the path, for example 'C:/test_folder/testing directory/test_1.csv'.

The test procedure creates an instance of the CSVReader called instance and creates a file from the test_1.csv. A two dimensional array of expected values is created called expResult.

The instance object is used to load the data and then the loaded two dimensional array is retrieved using the getData method. The returned array is then compared against the expected results which only returns true if the two arrays contain the same values.

Test for testGetData created
Figure 12

Test 2 - Parse fields by specified character with no leading or trailing white space.

Adjust the code in the method testParseLine to be consistent with that shown in Figure 13.

This method creates a line to be parsed, it the instantiates CSVReader into an object called instance. The results as they are expected to be returned are created and the parsed results are obtained from the parseLine method. The expected and actual results are compared to see if they are the same.

Test for parseLine created
Figure 13

Test 3 - Insert an empty string when an empty field is encountered whether at the beginning or end of line.

Create a new test method called testEmptyFields and insert the code as shown in Figure 14.

This method is almost identical to that in the testParseLine test method. The only difference is the input to the parseLine method and the expected result. The input and result is designed to ensure that empty fields are not ignored when at the beginning or end of a line.

This test could have been combined with the one above. However, as your test library grows for a procedure it is beneficial to break tests for specific behaviours into separate test methods so it is easy to see what behaviour is failing a test.

Test for parseLine looking for empty values created
Figure 14

Test 4 - Report an error if the number of fields is not consistent throughout the file.

Adjust the code in the method testLoadData to be consistent with that shown in Figure 15.

Download the test_2.csv test file and save it locally on your computer by right clicking the link and selecting Save Linked File As.... Adjust the line starting at 53 to point to the location of the file on your computer. Windows users will need to add the drive identifier at the start of the path, for example 'C:/test_folder/testing directory/test_1.csv'.

This test is more complex than the previous three. In order to report something has gone wrong with the field structures we need to raise an error in our loadData method when the problem is identified. Which means we need to test for the error here.

We use the try catch block to detect if the error is thrown, if it is we can then, check that the specific message matches the one to be used. If the error is not thrown of the message does not match then the code will fall continue on to the assert.fail statement and the test will fail.

Test for loadData created
Figure 15

Inserting an error to be thrown is a change to the method declaration so we need to add this to our loadData method to make sure all of our tests compile. Insert the code throws IOException immediately after the method declaration but before the opening brace as shown in Figure 16.

This raise an error mark on a suggestion light bulb in the left hand margin. Left click on the suggestion will provide the option to Add import for java.io.IOException, select this option and the IDE will add in the line of code for the import.

Adding error throw to the loadData method
Figure 16

Once the loadData method has been adjusted the CSVReaderTest test class will show an error. Navigate to the line causing the problem and you will see a suggestion light bulb in the left hand margin. Click on this and you will be provided with a list of three options:

  1. Add throws clause for java.io.IOException
  2. Surround statement with try-catch
  3. Surround block with try-catch

Adding the throws clause to the method declaration means that everywhere that this method is called needs to handle the potential exception. In this case we can select the second option Surround statement with try-catch and the IDE will fill in the try-catch code for us resolving the issue. Replace the automated logging line in the catch clause with a call to fail the test as shown in Figure 17.

Adding error handline to the getData test
Figure 17

That is all of the CSVReader test implemented. To run the tests, right click on the CSVReader class in the project area and select Test File.

The output below the text editor will look something like Figure 18. This shows that all of the tests have failed. Now we need to write the code in the two methods to satisfy the test conditions.

Output of running JUnit tests
Figure 18

The first part of this practical has created a class structure for importing and exporting data. It has also guided you through designing tests for both the reading and writing process. The final section implemented the four test conditions for the CSVReader. The next section will develop the code to satisfy the tests stage by stage.

Continue to part 2 of the practical