Search This Blog

Loading...

Saturday, February 11, 2012

Test: JUnit 4 Do's and Don'ts (An important notes)

DEFINITION:

A unit test examines the behavior of a distinct unit of work. Within a Java application, the “distinct unit of work” is often (but not always) a single method. By contrast, integration tests and acceptance tests examine how various components interact. A unit of work is a task that isn’t directly dependent on the completion of any other task.
A JUnit 4 test case class skeleton:

1.Do not name your test main class Test.java;
Which has the same short name as @Test annotation. Unless you explicitly use @org.junit.Test, @Test will resolve to your test class, instead of org.junit.Test annotation type.

2.Avoid using Java assertion;
Use static assertXXX methods (statically imported) in org.junit.Assert class. Java assert is turned off by default, and can be enabled only at command line with "java -ea", or "java -enableassertions" options. So some tests using Java assert will always pass when running with the default configuration, regardless of the actual test conditions. For example, the following method passes unless -ea option is passed to java:


A secondary reason to favor org.junit.Assert.assertXXX over Java assert is, the former is a richer API with more descriptive output, whereas Java assert, in its common form, only fails with java.lang.AssertionError and stack trace with no description. Java assert can also provide details with a second param: assert false : reason.

Maven surefire plugin automatically enables Java assertions when running JUnit tests on JDK 1.4 and above. So when running with surefire, there is no problem of false positive test results. But still I would like my JUnit tests to work the same way across all testing environment.

3.Avoid using assertTrue(list1.equals(list2));
Use assertEquals(list1, list2) instead. assertTrue will only print out the useless message like "expecting true, but actual false", whereas assertEquals gives more helpful message like "expected:[null, 1, 2], but was:[1, 2]"

4.Do not add assertTrue(true) at the end of a @Test
To signal the success result. It's just not needed. The test simply passes when nothing goes wrong.

5.@Test method can declare throws clause;
e.g., throws NamingException. When an exception is thrown during test run, the test is failed. So use exception as a means to communicate test failures. Do not try-catch an exception, just to print it and manually fail() it. For example, do this:

Do NOT do this:

When running the try-catch version, ArithmeticException stack trace is printed, followed by another stack trace of java.lang.AssertionError from calling fail(), whereas in the shorter version, only the ArithmeticException is logged and hence much easier to trace.

6.How to run a single test?
org.junit.runner.JUnitCore, the default command-line runner, takes a list of test classes as input, but doesn't support running a single test method. Other tools and IDE may support it by providing their own runner. With maven surefire 2.7.3+ and JUnit 4 tests, it is supported with a -D sysprop (link):

It is preferable to put the test name at the very end of the line so it's easier to go through the history and edit command.

7.To check the version of your JUnit:

8.To skip or exclude a test;
Use @Ignore (org.junit.Ignore).

9.To test a performance;
Use @Test(timeout = xx) (org.junit.Test) specify timeout in milliseconds to cause a test method to fail if it takes longer than that number of milliseconds.
For example: if you test a database query and it should take by max 1 second run this query inside a test case with timeout of 1000 ms, and tweak it further to reach this goal.

10.To test throwing a certain exception;
Use @Test(expected = xx) (org.junit.Test) where xx is a Throwable or sub of it, to cause a test method to succeed if an exception of the specified class is thrown by the method.

11.To test suite of test classes;
Use @RunWith(Suite.class) (org.junit.runner.RunWith) and @Suite.SuiteClasses( { ATest.class, A1Test.class }) (org.junit.runners.Suite) on class level as suite class that runs multiple test classes at the same time, in order they specified in the SuiteClasses, so ATest class test cases will run first before A1Test test cases.

The test suite could be empty and just for groping related test classes.

12.To test suite of suites;
Use 
@RunWith(Suite.class) (org.junit.runner.RunWith) and  @Suite.SuiteClasses( { ATestSuite.class, BTestSuite.class })(org.junit.runners.Suite) on class level as suite master class that runs multiple suite classes at the same time, in order they specified in the SuiteClasses, so ATestSuite class test cases for all classes it includes will run first before BTestSuite test cases.

The test suite could be empty and just for groping related test classes.

13.Running parameterized tests;
Use @RunWith(value = Parameterized.class) (org.junit.runner.RunWith) and @Parameters(org.junit.runners.Parameterized.Parameters) on a class method with signature "public static Collection name(){}" contains array of test parameters.

The Parameterized test runner allows you to run a test many times with different sets of parameters. Test class should have a constructor with argument list parameters equals to parameterized method array value.

When running a parameterized test class, instances are created for the cross-product of the test methods and the test data elements. Each instance of test class will be constructed using the n-argument constructor and the data values in the @Parameters method.

As the following example:

This test class will run and resulting with three test cases to run as testAdd[0], testAdd[1], testAdd[2].

Other testing types is shell and mock testing.

References:
1- JUnit in Action 2nd edition by (Petar Tahchiev,Felipe Leme,Vincent Massol,Gary Gregory).
2- Java how to Junit notes.