In the previous lesson we saw how to document your classes using the Javadoc tool.
How to test a class using JUnit
Testing that your classes do what they are supposed to is an essential part of software development. In practical terms, it is impossible to ensure that software is totally bug-free; however, with good testing you can be reasonably sure that your application will be sufficiently reliable for use in the real world. While testing can often be seen as a tedious, though necessary, chore, it is possible to create special Java classes that can help to automate the process of testing and re-testing your system. To do this you will make use of the JUnit testing tool which is installed as part of NetBeans. Again, you will use the Email utility class as an example.
Right-click the Email.java node in the Projects window and select Tools | Create JUnit Tests. The first time you do this you will be asked what version of JUnit to use, so you should select JUnit 4.x.
In the Create Tests dialog, make sure you deselect the check-box for Default Method Bodies:
Click OK.
A new Java class is automatically created called EmailTest. It is in a separate area under Test Packages in the Projects window, which mirrors the package structure of the class it is testing. Expand this node until you can see EmailTest.java. Locate the testGetEmail() method inside this class, which is initially empty:
- The
@Testannotation is used by JUnit to indicate that this is a testing method that it should call - The method name begins with
test, and is followed by the name of the actual method to be tested
Inside the method you need to create the state of the object(s) that are relevant to the test (which is known as its fixture), and then perform the test by invoking the appropriate method. Finally, you make an assertion that something should be the case:
- The fixture is set-up by instantiating an
Emailobject with some sample data - The method being tested is invoked, which in this case returns a
String - The Java
assertstatement checks whether the returned value matches what you expect it to be. It is made up of two parts separated by a colon character: - The first part is an expression which returns a
booleanvalue – here it is theequals()method ofString - The second part is a message that informs the programmer if the assertion failed, that is, the actual value did not match the expected value, to help in the location of the problem
To run the test, right-click on the EmailTest.java node in the Projects window and select Run File[1]. The test should succeed, leading to the following shown on the Output window:
The reason it states that seven tests have passed is that there are seven test methods inside EmailTest.java, although you have only coded one, the other six being currently empty.
Here is some test code for testGetLocalPart():
You may have noticed that the code for the fixture is identical to that in testGetEmail(). This is a common occurrence when testing, and to reduce the amount of code duplication you can move shared fixture code so that the Email object becomes an instance variable which is instantiated in the setUp() method which appears above the test methods in the source file. You will now refactor the EmailTest class to do this:
Define an instance variable:
Instantiate it in a method called setUp() method:
You now need to remove the fixture set-up lines from the testGetEmail() and testGetLocalPart() methods so they read as follows:
Run the test to verify that all seven tests are still successful.
It is important to note that when JUnit runs your test, the setUp() method is automatically invoked just before each individual test method. This means that if any of your test methods change the state of an object in the fixture it will be reset before the next test method is run. Because the Email class is immutable this is not an issue here but is something you need to be aware of when testing mutable classes.
Do not confuse setUp() with setUpClass(). The former is invoked before each individual test method but the latter only once before the very first test.
The tearDown() and tearDownClass() methods are only needed if you need to close objects like file resources. They are not needed here.
It will be helpful to see what happens if an assertion fails, so you should now introduce a temporary bug that the testing will catch. In the Email class change the getEmail() method to wrongly return email as a String constant rather than as a variable reference, to simulate a programmer mistake:
Run the test and note the results in the Output window:
From the above you can see the method which failed its test. This can help you pinpoint where the error is by tracing the lines of code which lead up to it. Change the getEmail() method back to correct the bug.
The testGetDomainPart() method will be similar to testGetLocalPart():
The testToString() method:
In order to test the equals() method you need two separate tests; one where the method should return true and the other where it should return false. While you could accomplish this inside a single test method it is also possible to split them into separate methods. Rename the testEquals() method to test1Equals(), and then include the following statements:
Now define a test2Equals() method that tests for when the equals() method should return false:
- Note the
!operator to negate the test.
Because hashCode() should work in harmony with equals() you can also split this into two test methods:
While the hash code should be the same when two objects are equal according to the equals() method, technically the hash codes don't have to be different for unequal objects. It is, however, the ideal to strive for and is therefore a useful test to include in case it pinpoints a problem with your hashCode() algorithm.
The testing of compareTo() needs to account for three situations:
- Where the objects being compared are equal
- Where the first object comes "after" the second object
- Where the first object comes "before" the second object
You may have noticed that no test method shell was generated for the validate() method of Email. This is because validate() was defined as private and therefore not directly accessible to the test class. It still needs to be tested somehow, though, and since it is invoked through Email's constructor you can define new test methods to do so.
The first check made by validate() is that the email string contains no spaces, so define this test method:
- Note that there is a space at the start of the email address, and therefore the test succeeds when an exception is thrown, hence the
try...catchstatements and emptycatchblock. If the expected exception is not thrown then the statement following the instantiation ofotherEmailwill be executed, so you can force notification by usingassert false.
The previous method tested for a space at the beginning of the email string, so you also need to test for a space at the end and in the middle:
The second check made in validate() is that there is exactly one @ character, so you should test where there are none, one, and more than one:
The third check made in validate() is that the local-part is not empty:
The fourth check made in validate() is that the domain-part contains at least one dot:
The fifth and final check made in validate() is that each sub-part of the domain-part is not empty:
Make sure all the tests succeed before continuing. If you are seeing testing failures, then it is most likely that you have mistyped something in either the EmailTest.java or Email.java.
When you created the EmailTest.java class you did so as an individual case, but in practice you will need to test all of your classes. JUnit allows you to define a test-suite which will automatically run the tests on all of your classes. In NetBeans, right-click on the Source Packages node of the Utilities project in the Projects window and select Tools | Create JUnit Tests. You will note in the Create Tests dialog that there are some additional options available, including one called Generate Test Suites. You should ensure this is checked and then click OK.
NetBeans will generate test classes for all classes in your project, although it won't overwrite the tests you have already defined in the EmailTest.java class[1]. It will also generate an additional class called UtilSuite.java (the prefix Util is taken from the last part of the package name), and because your package name consists of three parts (com, example & util) will also generate a suite class in each.
Open UtilSuite.java to see the following just before the class header:
The @Suite.SuiteClasses annotation is used to tell JUnit which classes should be tested, and as you can see it has automatically included all the individual classes in the package. You don't need to make any changes to this file, but bear in mind that if you create a completely new class in the future together with a test class then you will need to add an entry for it to the list in UtilSuite.java.
Under the Test Packages node open the <default package> node, and then open the RootSuite.java source file. This is the class it is recommend to run whenever you want to test the entire project, since it runs all the other suites and their referenced classes for you. If you run it now, all should pass, since you haven't written the individual test methods for any class other than EmailTest.java.
Purely for reasons of space, this course will not include any further test classes.
In the next lesson you will learn hoe to debug a class.