2.2.6 Detailed explanation of notes

Junit4 annotations provide basic functionality for writing unit tests. In this section we introduce the basic annotations @beforeClass, @afterClass, @before, @After, and @test.

 

@ BeforeClass annotations

A method annotated by @beforeClass is executed only once and is the first method executed when the junit test class is run. Such methods are used to perform computationally expensive tasks, such as opening database connections. Methods annotated by @beforeClass should be static (that is, of static type).

 

@ AfterClass annotations

Methods annotated by @AfterClass are also executed only once, and running the junit test class is the last method to be executed. This type of method is used to perform tasks like closing database connections. Methods annotated by @afterClass should be static (that is, static).

 

@ Before annotations

A method annotated by @before is a method that will be executed Before any test method in junit’s test class executes, and this type of method can be used to initialize the required resources for the test method.

 

@ After annotation

A method annotated by @After is a method that will be executed After any Test method in junit’s Test class executes, even if an exception is thrown by a Test method decorated with @test or @before. Methods of this type are used to close resources opened by test methods decorated with the @before annotation.

 

@ Test annotation

The Test method annotated by @test contains the actual Test code and is applied by Junit as the method to be tested. The @test annotation takes two optional arguments: Expected indicates the exception that should be thrown after this Test method executes (the value is the exception name); Timeout Checks the execution time of the test method.

 

@ Ignore annotations

Test methods annotated by @Ignore contain test code that is ignored and will not be run during testing.

 

To verify this, we’ll create a new JDemo class as follows:

package com.mooctest.util; class JDemo extends Thread { int result; public int add(int a, int b) { try { sleep(1000); result = a + b; } catch (InterruptedException e) { } return result; } public int division(int a, int b) { return result = a / b; } } package com.mooctest.util; import static org.junit.Assert.*; import org.junit.*; Public class JDemoTest {// test BeforeClass annotation @beforeClass public static void setUpBeforeClass() throws Exception {// Test BeforeClass annotation @beforeClass public static void setUpBeforeClass() throws Exception { System.out.println("in BeforeClass================"); @afterClass public static void tearDownAfterClass() throws Exception {system.out.println ("in ") AfterClass================="); } @before public void Before () {system.out.println ("in Before"); } @after public void After () {system.out.println ("in After"); } @test (timeout = 10000) public void testAdd () {JDemo a = new JDemo(); assertEquals(6, a.add(3, 3)); System.out.println("in Test ----Add"); } @test public void testDivision () {JDemo a = new JDemo(); assertEquals(3, a.division(6, 2)); System.out.println("in Test ----Division"); } @ignore @test public void test_ignore() {JDemo a = new JDemo(); assertEquals(6, a.add(1, 5)); System.out.println("in test_ignore"); } / / Test abnormal annotation @ Test (expected = ArithmeticException. Class) public void testException () {assertEquals (2, new Calculate().divide(6, 0)); } // Test Fail @test public void teest_fail() {Fail (); }}Copy the code

Running the code through Eclipse, we should get the following output on the console:

in BeforeClass================

in Before

in After

in Before

in Test ----Add

in After

in Before

in After

in Before

in Test ----Division

in After

in AfterClass=================
Copy the code

Junit’s test results are shown in Figure 2.20:

Figure 2.20 Running result of annotation program

As you can see, both the add and division methods of the test are fine. Because we caught the exception correctly, the application for testing the exception also runs successfully. The method annotated by Ignore is ignored by the system.

2.2.7 Failure and Error

There are two Error modes in the Junit module, Failure and Error. Failure is usually caused by the assertion method used by unit tests to determine Failure. This means that the test point has found a problem, and that the output of the program is not what we expected. Error is caused by code exceptions. It can produce errors with the tested code itself, or it can be a hidden bug in the tested code. For software testers, test cases are not used to prove you right, but to prove you are not wrong.

For a simple calculation, let’s create a simple Calculate class as follows:

package com.mooctest.util; public class Calculate { public int add(int a,int b){ return a+b; } public int divide(int a,int b){ return a/b; }} For this class, we create a new test case that tests Failure and Error as follows: package com.mooctest.util; import static org.junit.Assert.*; import org.junit.Test; import com.mooctest.util.Calculate; Public class ErrorAndFailureTest {// Test error, Failure@test public void testAdd(){assertEquals(5,new Calculate().add(3, 3)); Error @test public void testDivide(){assertEquals(2,new Calculate().divide(6, 0)); }}Copy the code

2.2.8 Parameterized test

Junit 4 introduces a new functional parameterization test. Parameterized testing allows developers to run the same test over and over again with different values. Because different parameter values can produce different results for the same test method, we write out multiple parameter values and perform assertion tests for completeness. Parameterized testing is like passing a set of “input values, expected values” to the test method for the purpose of one-time testing.

You will follow 5 steps to create parameterized tests:

1. Annotate the test class with @runwith (Parameterized. Class).

2. Create a public static method annotated by @parameters that returns a collection of objects (an array) as a collection of test data.

3. Create a public constructor that accepts the same thing as a row of test data.

4. Create an instance variable for each column of test data.

5. Create your test cases using instance variables as a source of test data.

Using a simple Fibonacci sequence class as an example, we will create a new Fibonacci class as follows:

package com.mooctest.util; class Fibonacci { public static int compute(int input) { int result; switch (input) { case 0: result = 0; break; case 1: case 2: result = 1; break; case 3: result = 2; break; case 4: result = 3; break; case 5: result = 5; break; case 6: result = 8; break; default: result = 0; } return result; }}Copy the code

FibonacciTest = FibonacciTest = FibonacciTest = FibonacciTest = FibonacciTest = FibonacciTest = FibonacciTest

package com.mooctest.util; import static org.junit.Assert.*; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class FibonacciTest { @Parameters(name = "{index}: fib({0})={1}") public static Iterable<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}}); } private int input; private int expected; public FibonacciTest(int input, int expected) { this.input = input; this.expected = expected; } @Test public void test() { assertEquals(expected, Fibonacci.compute(input)); }}Copy the code

 

The Junit test results are shown in Figure 2.21.

Figure 2.21 Parametric test results

2.2.9 Packaging Test

When we are faced with a large test project, there will often be many test cases in the project, if one test seems to be troublesome, so the packaged test function appears. As the name implies, a packaged test is a one-time test that completes all of the test cases contained in the package.

Taking the three test cases CalculateTest, FibonacciTest and JDemoTest as examples, we created a packaged test to test them with the following code:

package com.mooctest.util;

 

import static org.junit.Assert.*;

 

import org.junit.AfterClass;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.junit.runners.Suite;

import org.junit.runners.Suite.SuiteClasses;

 

 

@RunWith(Suite.class)

@SuiteClasses({CalculateTest.class,FibonacciTest.class, JDemoTest.class})

 

public class SuiteTest {

 

}
Copy the code

When the packaged test is completed, the packaged test results are the same as the single test results, as shown in Figure 2.22:

Figure 2.22 Package test results

2.2.10 Exception Handling

Unit tests are primarily used by developers to validate code, and many times unit tests can check code that throws expected exceptions. In the face of exceptions, we need to give the correct exception handling method. In the Java language, JUnit is a standard unit testing scheme that provides a number of mechanisms for validating thrown exceptions.

Taking the code below as an example, write a test to ensure that the canVote() method returns true or false, and you can also write a test to verify that the method throws an IllegalArgumentException.

public class Student { public boolean canVote(int age) { if (i<=0) throw new IllegalArgumentException("age should be +ve"); if (i<18) return false; else return true; }}Copy the code

There are three ways to check for thrown exceptions, each with advantages and disadvantages:

 

1. @ Test (expected)…

The @test annotation has an optional argument, “Expected” allowing you to set a subclass of Throwable. If you want to verify that the above canVote() method throws the expected exception, we can write:

@Test(expected = IllegalArgumentException.class)

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    student.canVote(0);

}
Copy the code

This is a straightforward way to check for exceptions, but the downside is that the test is a bit of an error because exceptions are thrown somewhere in the method, but not necessarily on a particular line.

 

2.ExpectedException

Before using the ExpectedException class in the JUnit framework, you need to declare the ExpectedException is abnormal.

 

@Rule

public ExpectedException thrown= ExpectedException.none();
Copy the code

You can then verify the expected exception in a simpler way.

 

@Test

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    thrown.expect(IllegalArgumentException.class);

    student.canVote(0);

}
Copy the code

Or you can set the attribute information for the expected exception.

 

@Test

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    thrown.expect(IllegalArgumentException.class);

    thrown.expectMessage("age should be +ve");

    student.canVote(0);

}
Copy the code

In addition to being able to set the attributes of the exception, this method has the advantage of being more precise in finding where the exception was thrown. In the above example, an unexpected IllegalArgumentException thrown in the constructor will cause the test to fail, and we want it to be thrown in the canVote() method.

3.Try/catch with assert/fail

Prior to JUnit4, the try/catch statement block was used to check for exceptions

 

@Test

public void canVote_throws_IllegalArgumentException_for_zero_age() {

    Student student = new Student();

    try {

        student.canVote(0);

    } catch (IllegalArgumentException ex) {

        assertThat(ex.getMessage(), containsString("age should be +ve"));

    }

    fail("expected IllegalArgumentException for non +ve age");

}
Copy the code

Although this method is very old, it is very effective. The main drawback is that it is easy to forget that you need to write the fail() method after a catch block, which can result in false positives if the expected exception is not thrown. I’ve made that mistake before.

In summary, all three methods can test for expected exceptions, and each has its advantages and disadvantages. You can choose to use it according to your preferences.

 

2.2.11 Time-limited test

In code development, we often encounter dead loops or methods that run for too long. In order to prevent dead loops or check method efficiency, we need to use timed tests, that is, exceeding the set time is regarded as a test failure. There are two ways to write it.

The first is written as follows:

@Test(timeout=1000)  

public void testWithTimeout() {  

  ...  

}  
Copy the code

After the Test annotation, add an implementation of the Test, say a thousand milliseconds.

 

The second rule is similar to the second method of exception handling. It is written as follows:

public class HasGlobalTimeout { public static String log; @Rule public Timeout globalTimeout = new Timeout(10000); // 10 seconds max per method tested @Test public void testInfiniteLoop1() { log += "ran1"; for (;;) { } } @Test public void testInfiniteLoop2() { log += "ran2"; for (;;) {}}}Copy the code