“No cameras!” 📸 Has anyone ever said this to you?
Have you ever gone into off-limits areas at a museum? 🚫 Have you taken a picture where it wasn’t permitted? How did you avoid parking in the chief curator’s parking slot? Signs, right?
Signs can save you a lot of trouble by helping you do the right thing. Java also gives special signs called annotations which help you tell your code how it should behave. They start with an @ and each has its own special meaning.
You’ve already seen one! @Test
is a JUnit 4 annotation which can be put before a method. With @Test
, JUnit will immediately know that of all the methods in that file, this one is supposed to be run as a test. It will take the name of the method and make it the name of the test.
There are many useful annotations which come with JUnit, and I’ll introduce some of these to you. They can help make your tests super powerful and save you lines of code! To use these annotations, drop them into your test class, a little like putting up a new sign in a museum.
Let’s annotate!
I bet you’re dying to see JUnit’s annotations in action! Let’s dig in!
We’re going to add a few annotations to our CalculatorTest. The test class should be named after the class under test (CUT). Since we’re testing a calculator, let’s walk through a CalculatorTest which uses the basic annotations of: @BeforeClass
, @AfterClass
, @Before
, @After
, and some special spins on the @Test
annotations:
public class CalculatorTest {
private static Instant startedAt;
private Calculator calculatorUnderTest;
We have added a static variable and class field to use in the example above. Now, let's check out @BeforeClass
:
@BeforeClass
public static void beforeClass() {
//let's capture the time when the test was run
System.out.println("Before class");
statedAt = Instant.now();
}
@BeforeClass
will cause a method to run once before our first test. In this example, we store the time when the tests were loaded. Next is @AfterClass
:
@AfterClass
public static void afterClass() {
// Not the way to do it. Just illusrating @AfterClass
// How long did the tests take?
Instant endedAt = Instand.now();
Duration duration = Duration.between(startedAt, endedAt);
System.out.println("Tests took" + duration.toString());
}
@AfterClass
marks a method to be run once after our last test has completed. This will run even if the tests fail. Here we measure how long it took to run the tests.
Next is @Before
:
@Before
public void setUp() {
// Recreate the calculator before each test
calculatorUnderTest = new Calculator();
System.out.println("Before Test:" + Instant.now());
}
@Before
marks a method so it can be run before each @Test
. Here we make sure that we’ve created a new instance of Calculator.
Now we've got @After
:
@After
public void tearDown() {
// set calculator to null after each test
calculatorUnderTest = null;
System.out.println("After Test" + Instant.now());
}
@After
marks a method to be run as soon as JUnit has finished running a @Test
. This is often used to clean up things done in @Before
. You’ve already seen the @Test
annotation. Let’s look at some options to make testing even simpler:
As you saw, the assertion to test for an exception can be passed directly to @Test
. Use the expected argument as well as the value of the exception class you expect to see being thrown.
@Test(expected=Exception.class)
public void add_returnsTheSum_ofTwoPositiveNumbers() throws Exception {
Double expected = 3.0;
Double sum = calculatorUnderTest.add(left:1.0, right:2.0);
assertThat(expected,is (equalTo(sum)));
//prentend this was from your code!
throw new Exception ("We expect an Exception");
}
@Test(expected=<Exception.class> )
passes some options to @Test
which allow you to test sad paths where you expect a particular exception to be thrown.
Let’s look at how tests can let you know if the methods they are testing suddenly become too slow:
As you saw, you can pass @Test
the timeout argument with a value of the time, in milliseconds, which the test should not exceed. That way, your tests will tell you if you ever put a slow algorithm into your code!
@Test(timeout=1000)
public void add_returnsTheSum_OfTwoNegativeNumbers() throws Exception {
Double expected = -3.0;
Double sum = calculatorUnderTest.add(left:-1.0, right:-2.0);
assertThat(expected, is(equalTo(sum)));
// Let's timeout our test
Thread.sleep(millis:2000);
}
@Test(timeout=1000)
fails the test marked with this annotation if it takes longer than 1000ms (1 second). Can you see that the test is rigged to fail?
This is what happens when you run it:
Try and match up the printed messages (e.g., before class in the right panel) with the steps in the code above. Can you see which test failed?
It's your turn now!
Check out the repository and try running these tests for yourself.
git clone https://github.com/OpenClassrooms-Student-Center/AchieveQualityThroughTestingInJava.git git checkout p1-c5-sc2-to-sc3
Or, use your IDE to clone https://github.com/OpenClassrooms-Student-Center/AchieveQualityThroughTestingInJava.git and check out the branch p1-c5-sc2-to-sc3.
You can now explore the examples from the screencast and try running the tests with:
mvn test
Let's recap!
JUnit’s annotations help you write clearer tests without unnecessary repetition.
Some common annotations are:
Annotation | When to use it |
| If testing method which should not be too slow, you can force it to fail the test. |
| Use this annotation to test if your class throws an exception. Don’t use try/catch to test for it. |
| Run a method prior to each test. This is a great place to set up or to arrange a precondition for your tests. |
| Run a method after each test. This is a great place to clean up or satisfy some post-condition. |
| Mark a static method so it runs before all your tests. You can use this to set up other static variables for your tests. |
| Mark a static method as being run after all your tests. You can use this to clean up static dependencies. |
Now that you know how to do unit testing, let's make it even easier by adding some effective plugins...in the next chapter! See you there!