Lab 12: An Introduction to the JUnit testing library
Goals: The goal of this lab is to transition from the tester library to the JUnit testing library.
You may work with your current assignment partner in this lab. One submission per team is enough. Please submit what you’ve completed by the end of the lab period on handins.
Problem 1: From the tester library to JUnit
12.1 Introduction to JUnit
All through this semester we have been using the tester library. This is a library written specifically for CS 2510. One of its primary objectives is to help you write meaningful and non-trivial tests while mimicking the general style of test writing in Racket. In this lab we will transition from this custom testing library to a more popular and widely used testing library: JUnit.
JUnit provides a testing framework for Java code. It is widely used in academic and industry settings, and is a powerful library. CS 3500 uses JUnit as well. Unlike existing Java classes that we have been using all semester, JUnit is not part of the JDK. Therefore JUnit must be downloaded separately. More information can be found at the JUnit library website. However most IDEs (including Eclipse) ship with JUnit, so you do not have to download it separately if you are using any of them. There are several similarities between our tester library and JUnit, both in terms of how it works and how to use it.
In this lab we will start with the code from Lecture 4 (linked above), and we will replicate and then add to the tests written in this code using JUnit.
12.2 Preparing your Project
Create a new project in Eclipse, and put the above file in it.
Add the tester library to the project, and create a Run configuration for it.
Run the configuration to ensure that all tests run and pass.
Create a new file called "ExamplesShapesInJUnit.java". Copy the example class code from where it is to this file, and rename the class to ExamplesShapesInJUnit.
Make the class public by adding the word public before class Examples....
Make its constructor public.
Rerun the configuration to ensure that all tests run and pass.
- Add JUnit to the project:
Right click on the project and go to Properties
Go to Java Build Path (these two steps are the same to add the tester library)
Select "Classpath" and then click on "Add Library"
In the dialog box, select JUnit and click "Next"
From the dropdown menu, select "Junit 4" (even though JUnit 5 is the latest version we will use JUnit 4)
Click "Finish". You should now see the JUnit4 library added to Classpath (along with our tester library)
12.3 A JUnit test
The tester library identifies a test method by its name: the name must begin with "test" and should have one argument of type Tester. A JUnit test is a public method inside a public class. Such a method returns void and takes no arguments. Its name does not matter. However one must annotate the method to identify it as a test.
All the instructions below are for the ExamplesShapesInJUnit class.
Find the method testCircleContains. We will convert this method to a JUnit-equivalent of itself.
Make the method public, change its return type to void and fix the method implementation so that it does not return anything.
Add the following right above its method signature: @Test. This annotation above a method tells JUnit that it is JUnit test method.
Add import org.junit.Test; to the file at the top, so that it recognizes the annotation.
12.3.1 assertEquals
The JUnit equivalent of t.checkExpect is assertEquals. It takes two arguments: the expected value and the actual value in that order. Note that this order is flipped from checkExpect.
Replace t.checkExpect(this.c1.contains(new CartPt(100, 100)), false) with assertEquals(false,this.c1.contains(new CartPt(100,100))).
Add import static org.junit.Assert.assertEquals; to the top of the file.
Replace the second checkExpect with assertEquals accordingly.
To run the tests, right-click on this file from the Package Explorer, and select "Run As" -> "JUnit Test".
You should now see a new tab open up, and a report that states that it ran 1/1 tests and all passed with a green progress bar. Congratulations! You just ran your first JUnit test!
Change the expected value of the first assertEquals to true and run the test again. See what a failure looks like.
Change the first assertEquals to assertEquals("The contains method for circle does not work properly", false,...). Rerun your test to see this message appear.
This version of assertEquals allows you to specify a string that is printed only if the test fails. You can use this string to help you debug.
Change the test back so that it passes once again.
12.3.2 assertTrue and assertFalse
If your expected and actual values are boolean you can use assertTrue and assertFalse instead.
Replace both assertEquals statements above with assertTrue or assertFalse as applicable. You will have to add the corresponding imports.
Run the tests to make sure they pass.
12.3.3 Different flavors of assertEquals
There are several different versions of the assertEquals method.
Find the test testCircleGrow. Change its method signature to make it public and add the @Test annotation.
Change the test to its JUnit equivalent using assertEquals statements.
Run the tests. This test fails!
The test fails because assertEquals uses intensional equality, and therefore two equivalent Circle objects are deemed to be not the same. It uses intensional equality because it uses the equals method for one of the objects and passes the other object to it! Since we did not write an equals method in the Circle class (why would we? We were so naive back then!), it uses the default implementation that compares references.
Write an equals method in the Circle class.
Now run the test again. This time it should pass!
The assertEquals method can be used to compare two double values as well (equivalent to checkInExact).
Find the test testDistToOrigin. Change its method signature to make it public and add the @Test annotation.
Replace t.checkInexact(this.pt1.distToOrigin(), 0.0, 0.001) with assertEquals(0.0,this.pt1.distToOrigin(),0.001);.
As before, the expected and actual values are flipped. But the third argument is the error threshold, exactly like checkInExact.
Replace the second test with assertEquals.
Run the test and verify that it passes.
If you wish to add a String message to this version, you can add it as the first argument as before.
Convert other test methods one-by-one to their JUnit equivalents.
12.4 Timeouts
You can add a timeout value to verify that the test takes no more than a certain amount of time. If the test takes more than the allocated time, it will time out and fail. This is useful to check whether tests (and therefore the methods they test) are sufficiently efficient.
To make a test time out if it runs more than 3s, replace @Test above the test to @Test(timeout=3000).
12.5 Checking for an exception
You can write JUnit tests to check whether exceptions are thrown as expected.
Modify the constructor of the Circle class so that it throws an IllegalArgumentException if a negative radius is passed to it.
Write the following test:
@Test(expected=IllegalArgumentException.class) public void testCircleException() { IShape c = new Circle(30,30,-10,"blue"); } The Test(expected=IllegalArgumentException.class) states that this test will pass at the first instance it sees an IllegalArgumentException when the test is run. The test fails if it executes the test completely without encountering this specific exception.
Because it passes the first time it sees the specific exception, you should not try to test for multiple cases of exceptions inside the same test method.
- The above test is equivalent to the one below:
@Test public void testCircleException() { IShape c; try { c = new Circle(30,30,-10,"blue"); fail("This test did not throw any exception"); } catch (IllegalArgumentException e) {} catch (Exception e) { fail("This test did not throw the correct kind of exception"); } } The fail statement fails the test: it is the murdering hatchet of JUnit: no questions asked, no conditions checked, just fail!