Saturday, June 28, 2008

Junit 4

When Do You Need to Write an @After Method?

You need to consider the following points when anwering this question:

  • If there is no "@Before" method, you don't need to write an "@After" method. There is nothing to be released.
  • If the "@Before" method only creates Java internal objects, you don't need to write an "@After" method. Java internal objects will be released automatically by the Java garbage collector.
  • If the "@Before" method creates any external resources like files or database objects, you need to write an "@After" method to release those external resources.

Here is a good example of using "@After" methods from the JUnit FAQ:

package junitfaq;

import org.junit.*;
import static org.junit.Assert.*;
import java.io.*;

public class OutputTest {

private File output;

@Before
public void createOutputFile() {
output = new File(...);
}

@After
public void deleteOutputFile() {
output.delete();
}

@Test
public void testSomethingWithFile() {
...
}

@Test
public void anotherTestWithFile() {
...
}
}

How Do You Test an Unexpected Exception with JUnit?

If you want to test a method that could raise an unexpected exception, you should design a test that:

  • Put the test code inside a "try" block".
  • Catch any unexpected exception object.
  • Fail the test witl Assert.fail().

JUnit runner will fail this test if the test code raised any unexpected exception.

Here is a good test class that test any unexpected exception raised by the get() method of the ArrayList class:

import org.junit.*; import java.util.*; // by FYICenter.com public class UnexpectedExceptionTest1 { @Test public void testGet() { ArrayList emptyList = new ArrayList(); // catch any unexpected exception try { Object o = emptyList.get(1); } catch (Exception e) { Assert.fail("Unexpected exception"); } } }

If you run this test, it will fail:

java -cp .;junit-4.4.jar org.junit.runner.JUnitCore UnexpectedExceptionTest1 JUnit version 4.4 .E Time: 0.015 There was 1 failure: 1) testGet(UnexpectedExceptionTest1) java.lang.AssertionError: Unexpected exception at org.junit.Assert.fail(Assert.java:74) at UnexpectedExceptionTest1.testGet(UnexpectedExceptionTest1.ja at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAcce at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMe at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.internal.runners.TestMethod.invoke(TestMethod.java at org.junit.internal.runners.MethodRoadie.runTestMethod(Method at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.j at org.junit.internal.runners.MethodRoadie.runBeforesThenTestTh at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.jav at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMetho at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUni at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4Cla at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassR at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoa at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4Class at org.junit.internal.runners.CompositeRunner.runChildren(Compo at org.junit.internal.runners.CompositeRunner.run(CompositeRunn at org.junit.runner.JUnitCore.run(JUnitCore.java:130) at org.junit.runner.JUnitCore.run(JUnitCore.java:109) at org.junit.runner.JUnitCore.run(JUnitCore.java:100) at org.junit.runner.JUnitCore.runMain(JUnitCore.java:81) at org.junit.runner.JUnitCore.main(JUnitCore.java:44) FAILURES!!! Tests run: 1, Failures: 1

What Happens If a Test Method Throws an Exception?

If you write a test method that throws an exception by itself or by the method being tested, the JUnit runner will declare that this test fails.

The example test below is designed to let the test fail by throwing the uncaught IndexOutOfBoundsException exception:

import org.junit.*; import java.util.*; // by FYICenter.com public class UnexpectedExceptionTest2 { // throw any unexpected exception @Test public void testGet() throws Exception { ArrayList emptyList = new ArrayList(); Exception anyException = null; // don't catch any exception Object o = emptyList.get(1); } }

If you run this test, it will fail:

java -cp .;junit-4.4.jar org.junit.runner.JUnitCore UnexpectedExceptionTest2 JUnit version 4.4 .E Time: 0.015 There was 1 failure: 1) testGet(UnexpectedExceptionTest2) java.lang.IndexOutOfBoundsException: Index: 1, Size: 0 at java.util.ArrayList.RangeCheck(ArrayList.java:547) at java.util.ArrayList.get(ArrayList.java:322) at UnexpectedExceptionTest2.testGet(UnexpectedExceptionTest2.ja at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAcce at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMe at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.internal.runners.TestMethod.invoke(TestMethod.java at org.junit.internal.runners.MethodRoadie.runTestMethod(Method at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.j at org.junit.internal.runners.MethodRoadie.runBeforesThenTestTh at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.jav at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMetho at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUni at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4Cla at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassR at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoa at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4Class at org.junit.internal.runners.CompositeRunner.runChildren(Compo at org.junit.internal.runners.CompositeRunner.run(CompositeRunn at org.junit.runner.JUnitCore.run(JUnitCore.java:130) at org.junit.runner.JUnitCore.run(JUnitCore.java:109) at org.junit.runner.JUnitCore.run(JUnitCore.java:100) at org.junit.runner.JUnitCore.runMain(JUnitCore.java:81) at org.junit.runner.JUnitCore.main(JUnitCore.java:44) FAILURES!!! Tests run: 1, Failures: 1

How Do You Test a Method That Doesn't Return Anything?

You need to follow the logic below to answer this question:

  • If a method is not returning anything through the "return" statement (void method), it may return data through its arguments. In this case, you can test the data returned in any argument.
  • Else if a method is not returning any data through its arguments, it may change values of its instance variables. In this case, you can test changes of any instance variables.
  • Else if a method is not changing any instance variable, it may change values of its class variables. In this case, you can test changes of any class variables.
  • Else if a method is not changing any class variable, it may change external resources. In this case, you can test changes of any external resources.
  • Else if a method is not changing any external resources, it may just doing nothing but holding the thread in a waiting status. In this case, you can test this waiting condition.
  • Else if a method is not holding the thread in waiting status, then this method is really doing nothing. In this case, there is no need to test this method. :-)

When Objects Are Garbage Collected After a Test Is Executed?

My guess would be that all objects used in a test will be ready for Java garbage collector to release them immediately after the test has been executed. But it seems that I was wrong. The JUnit FAQ provided the following answer.

By design, the tree of Test instances is built in one pass, then the tests are executed in a second pass. The test runner holds strong references to all Test instances for the duration of the test execution. This means that for a very long test run with many Test instances, none of the tests may be garbage collected until the end of the entire test run.

Therefore, if you allocate external or limited resources in a test, you are responsible for freeing those resources. Explicitly setting an object to null in the tearDown() method, for example, allows it to be garbage collected before the end of the entire test run.

If this is true, the JUnit runner should be improved to stop building all test instances before executing any tests. Instead, the JUnit runner should just take one test at a time, build an instance of this test, execute the test, and release the test when the execution is done.


What Is Java "assert" Statement?

"assert" statements are part of the Java assertion feature introduced in Java 1.4. Java assertion feature allows developer to put "assert" statements in Java source code to help unit testing and debugging.

An "assert" statement has the following format:

   assert boolean_expression : string_expression;

When this statement is executed:

  • If boolean_expression evaluates to true, the statement will pass normally.
  • If boolean_expression evaluates to false, the statement will fail with an "AssertionError" exception.

Here is a good example of an "assert" statement used to check an invariant:

// Day-of-week calculator
// by FYICenter.com

public class DayOfWeek {
private int days = 0;

// main method for testing purpose
public static void main(String[] arg) {
int d = Integer.parseInt(arg[0]);
DayOfWeek o = new DayOfWeek(d);
System.out.print("Day of the week: "+o.getDayOfWeek());
}

// constrcutor
public DayOfWeek(int d) {
days = d;
}

// calculate day of the week
public String getDayOfWeek() {
if (days % 7 == 0) {
return "Sunday";
} else if (days % 7 == 1) {
return "Monday";
} else if (days % 7 == 2) {
return "Tuesday";
} else if (days % 7 == 3) {
return "Wednesday";
} else if (days % 7 == 4) {
return "Thursday";
} else if (days % 7 == 5) {
return "Friday";
} else {
assert days % 7 == 6 : days;
return "Saturday";
}
}
}

How To Compile and Run Java Programs with "assert" Statements?

If you are using "assert" statements in your Java program for unit testing and debugging, you need to compile and execute the program with extra command options to use them:

  • Compilation - "javac -source 1.4 MyClass.java". This tells the compiler to accept source code containing assertions.
  • Executionn - "java -ea MyClass". This tells the JVM to not skill "assert" statements. "-ea" is the same as "-enableassertions".

Here is an example of compiling and executing the sample program, DayOfWeek.java, presented in the previous question:

javac -source 1.4 DayOfWeek.java

java -ea DayOfWeek 1
Day of the week: Monday

java -ea DayOfWeek 6
Day of the week: Saturday

java -ea DayOfWeek 14
Day of the week: Sunday

java -ea DayOfWeek -2
Exception in thread "main" java.lang.AssertionError: -2
at DayOfWeek.getDayOfWeek(DayOfWeek.java:34)
at DayOfWeek.main(DayOfWeek.java:11)

As expected, the code does not handle negative numbers correctly.

How To Test Programs Thats Use Java "assert" Statements?

Since JUnit 3.8, the JUnit runner if fully compatible with the assertion feature. If a Java "assert" statement failed in a JUnit test, the JUnit runner will report that this test fails.

When you design your tests, you should ignore the fact the target class is using "assert" statements. Ingore those "assert" statement and write tests to cover all cases where the target class could break.

Here is a good example of JUnit tests testing the sample program, DayOfWeek.java, presented in the previous question. test3() is designed to test the same area where the Java "assert" statement is located.

import org.junit.*;
// by FYICenter.com

public class DayOfWeekTest {

@Test public void test1() {
DayOfWeek d = new DayOfWeek(6);
Assert.assertTrue("Expecting Saturday",
d.getDayOfWeek().equals("Saturday"));
}

@Test public void test2() {
DayOfWeek d = new DayOfWeek(14);
Assert.assertTrue("Expecting Sunday",
d.getDayOfWeek().equals("Sunday"));
}

@Test public void test3() {
DayOfWeek d = new DayOfWeek(-2);
Assert.assertTrue("Expecting Friday",
d.getDayOfWeek().equals("Friday"));
}
}

Should You Run JUnit Tests with Java Assertion Disabled?

If your test class is well designed and covering all cases where the target class could break, you should run your test with Java assertion disabled by using the JVM "-da" option.

If you are using the test class, DayOfWeekTest.java, presented in the previous question, here is the error report when executed with the "-da" option, disabling Java assertion:

java -da -cp .;\local\junit4.4\junit-4.4.jar
org.junit.runner.JUnitCore DayOfWeekTest

JUnit version 4.4
...E
Time: 0.015
There was 1 failure:
1) test3(DayOfWeekTest)
java.lang.AssertionError: Expecting Friday
at org.junit.Assert.fail(Assert.java:74)
at org.junit.Assert.assertTrue(Assert.java:37)
at DayOfWeekTest.test3(DayOfWeekTest.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
FAILURES!!!
Tests run: 3, Failures: 1

test3() failed with the JUnit assertTrue() call, and caught the code problem with negative values.


What Happens If You Run JUnit Tests with Java Assertion Enabled?

As of JUnit 3.8, The JUnit runner will work nicely with Java assertion feature. If Java assertion is enabled and a Java "assert" statement fails, the JUnit runner will report this in same way as the calling test fails.

If you are using the test class, DayOfWeekTest.java, presented in the previous question, Here is the error report when executed with the "-ea" option, enabling Java assertion:

java -ea -cp
.;\local\junit4.4\junit-4.4.jar
org.junit.runner.JUnitCore DayOfWeekTest
JUnit version 4.4
...E
Time: 0
There was 1 failure:
1) test3(DayOfWeekTest)
java.lang.AssertionError: -2
at DayOfWeek.getDayOfWeek(DayOfWeek.java:34)
at DayOfWeekTest.test3(DayOfWeekTest.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0
(Native Method)
...

FAILURES!!!
Tests run: 3, Failures: 1

test3() failed with the Java "assert" statement inside the getDayOfWeek() method, not the JUnit assertTrue() call. In other work, the code problem is caught by the Java "assert" statement, not by the JUnit test.

Why Does JUnit Only Report the First Failed Assertion in a Single Test?

Reporting multiple failures in a single test is generally a sign that the test does too much, compared to what a unit test ought to do. Usually this means either that the test is really a functional/acceptance/customer test or, if it is a unit test, then it is too big a unit test.

JUnit is designed to work best with a number of small tests. It executes each test within a separate instance of the test class. It reports failure on each test. Shared setup code is most natural when sharing between tests. This is a design decision that permeates JUnit, and when you decide to report multiple failures per test, you begin to fight against JUnit. This is not recommended.

Long tests are a design smell and indicate the likelihood of a design problem. Kent Beck is fond of saying in this case that "there is an opportunity to learn something about your design." We would like to see a pattern language develop around these problems, but it has not yet been written down.

Finally, note that a single test with multiple assertions is isomorphic to a test case with multiple tests:

One test method, three assertions:

    public class MyTestCase {
@Test
public void testSomething() {
// Set up for the test, manipulating local variables
assertTrue(condition1);
assertTrue(condition2);
assertTrue(condition3);
}
}

Three test methods, one assertion each:

    public class MyTestCase {
// Local variables become instance variables

@Before
public void setUp() {
// Set up for the test, manipulating instance variables
}

@Test
public void testCondition1() {
assertTrue(condition1);
}

@Test
public void testCondition2() {
assertTrue(condition2);
}

@Test
public void testCondition3() {
assertTrue(condition3);
}
}

The resulting tests use JUnit's natural execution and reporting mechanism and, failure in one test does not affect the execution of the other tests. You generally want exactly one test to fail for any given bug, if you can manage it.


No comments:

Topics