JUnit Best Practices
1. Introduction
There are a lot of ways on how a developer can do JUnit test cases, but overall it really depends on the strategy of how a specific functional code can be tested to it’s limits. It’s the developers responsibility to introduce thorough test cases to make sure that the code is stable and ultimately does what it has to do.
In this blog post, I’ll share some of the best practices I have learned and discuss my experience on them.
2. Best Practices
The following best practices are a compilation of research and personal experience.
2.1 TDD approach. Create your test case before anything else.
In TDD, one usually starts with the test cases before coding the actual implementation. The test cases are expected to be created with respected and tightly coupled to how the system or the module was designed. This will then be the basis of the code implementation.
2.2 Avoid writing test cases that can cause impact
Test cases that can cause impact exhibit the following problems:
- Test cases that manipulates data
- Test cases that require manual intervention
Developers should avoid creating test cases that manipulate real data on any environment. This can cause inconsistencies of data in test environments or in the worst case scenario, manipulate real business case data into stale data. We should avoid this at all costs.
Test cases should be automatically be re-runnable in the exact same way, something that cannot be achieved if manual intervention is required by the developer. The idea behind running a test case is that the scenario should be executed as if it would be if called from a business case. We don’t want to do anything outside the scenarioas this will compromise the integrity of the test case results.
2.3 Never skip tests
In builds, developers can always include the test case runs. In Maven, test cases are executed by default when the install, prepare and perform goals are called. It’s always a great thing to have the test cases executed, as this will ensure that the functional aspects of the system are working as expected. Some of the major test cases can be incorporated into this scheme and that is something that developers should not overlook.
2.4 Sensible Test Case names!
All developers would agree on this. The test cases names should be valid and meaningful.
2.4.1 Create Order Implementation
createOrder()
public Order createOrder(Order order) { Order newOrder = new Order(); newOrder.setOrderId(new Random().nextInt()); newOrder.setSecurityCode("XYZ"); newOrder.setOrderStatus("INITIATED"); newOrder.setOrderDate(new Date()); orderDao.createOrder(newOrder); return newOrder; }
2.4.2 Create Order Test Case
testSampleServiceCreateOrder()
@Test public void testSampleServiceCreateOrder() { Order newOrder = new Order(); newOrder.setSecurityCode("XYZ"); newOrder.setDescription("Description"); if (newOrder != null) { assertThat(sampleService.createOrder(newOrder),instanceOf(Order.class)); assertNotNull("Security isn't null", newOrder.getSecurityCode()); assertNotNull("Description isn't not null",newOrder.getDescription()); } }
2.5 Always aim to do one assertion for each test method
One assertion = One test method. That’s the rule of thumb. If that rule is not followed try to break down the redundant scenarios.
testAssertEqualsFalseWithMessage()
@Test public void testAssertEqualsFalseWithMessage() { ServiceObject newServiceObject = new ServiceObject(); junitAssertEqualsServiceSample.postProcessing(serviceObject); assertEquals("Not the Same Object",newServiceObject,serviceObject); }
2.6 Assertions, maximize it!
The JUnit Assert package has a lot of methods that can be used to do test cases. Combine this with a hamcrest and you get an extremely powerful api that uses pattern matching utilities.
import JUnit utilities that can be used
import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.BaseMatcher.*;
2.7 Annotations
Java 5 has introduced us to the concept of Annotations. JUnit 3 started to compliment this new JDK something that worked perfectly. The following are the basic and mostly used annotations.
@RunWith
– Use to specify a runner object that the test case will be called from.@Test
– use to tag a method as a test case.@Before
– use to tag a method to be called before a test case. This usually being used to initialse data for the test case.@After
– use to tag a method to be called after a test case. This is usually used to do clean up processes.
There’s more useful annotations available and developers should maximise these.
2.8 Packaging convention
The package naming convention should always be directly the same to the one of the package of your implementation. This will make it more maintainable as it generally forms a pattern for developers once they need to update an existing test case.
2.9 Test code is separated from Production / release code
This is self-explanatory. You should always separate your test code and production code. Maven has a great way of separating this by introducing a package convention for test cases and implementation source files. I extremely suggest using Maven or Gradle as it generally setups everything for you.
2.10 Make use of @Before
, @After
– Never construct the test case class
Annotations again! @Before
and @After
are annotations that can be use to tag a method if you want it to be called upon initialization and destruction of test object respectively.
@Before public void setData(){ this.totalNumberOfApplicants = 9; listOfValidStrings.add("object_1"); listOfValidStrings.add("object_2"); listOfValidStrings.add("object_3"); } @After // tearDown() public void after() throws Exception { dummyAccount = null; assertNull(dummyAccount); }
2.11 Don’t just pass the test just for the sake of passing it!
Of course, everyone can just write ‘assert(true)’ just for the sake of passing a test case, but then what’s the point of doing it in the first place? The whole strategy of doing Junit test case is the fact that we want to ensure that the system is stable and scalable against any new extensions and requirements. It doesn’t make sense to just treat it as a way to manipulate SQM results.
@Test public void testGenerateAccount() { assert(true); // wow. don't do this. just don't }
2.12 Do not write your own catch blocks that exist only to pass a test
It is unnecessary to write your own catch blocks that exist only to pass a test because the JUnit framework takes care of the situation for you. For example, suppose you are writing unit tests for the following method:
final class Foo{ int foo(int i) throws IOException; }
Here we have a method that accepts an integer and returns an integer and throws an IOException
if it encounters an error. Further suppose that we expect the method to throw an IOException
if a parameter is passed with the value nine. Below you can find the wrong way to write a unit test that confirms that the method behaves that way:
// Don't do this - it's not necessary to write the try/catch! @Test public void foo_nine(){ boolean wasExceptionThrown = false; try{ new Foo().foo(9); } catch (final IOException e){ wasExceptionThrown = true; } assertTrue(wasExceptionThrown); }
Instead of manually catching the expected exception, use the expected attribute on JUnit’s @Test annotation.
// Do this instead @Test(expected = IOException.class) public void foo_nine() throws Exception { new Foo().foo(9); }
We declare that the test method throws Exception rather than IOException
– see below for the reason. The JUnit framework will make sure that this test passes if and only if the foo method throws an IOException
– there’s no need to write your own exception handling.
2.13 Do not write your own catch blocks that exist only to print a stack trace
As we’ve already seen, it is a best practice for unit tests not to write anything. After all, unit tests are written to be consumed by the JUnit framework and JUnit doesn’t care or monitor what gets printed. For example, suppose you are writing unit tests for the following method:
final class Foo { int foo(int i) throws IOException; }
Here we have a method that accepts an integer and returns an integer and throws an IOException
if it encounters an error. Here is the wrong way to write a unit test that confirms that the method returns three when passed seven:
// Don't do this - it's not necessary to write the try/catch! @Test public void foo_seven() { try{ assertEquals(3, new Foo().foo(7)); } catch (final IOException e){ e.printStackTrace(); } }
The method under test specifies that it can throw IOException
, which is a checked exception. Therefore, the unit test won’t compile unless you catch the exception or declare that the test method can propagate the exception. The second alternative is much preferred because it results in shorter and more focused tests:
// Do this instead @Test public void foo_seven() throws Exception { assertEquals(3, new Foo().foo(7)); }
We declare that the test method throws Exception rather than throws IOException
– see below for the reason. The JUnit framework will make sure that this test fails if any exception occurs during the invocation of the method under test – there’s no need to write your own exception handling.
2.14 Avoid the Threads sa much as possible
We don’t want to spawn multiple threads on your test case unless it’s really necessary and avoid pausing it (Thread.sleep
) as this will greatly impact the build time and execution. When a unit test uses Thread.sleep it does not reliably indicate a problem in the production code. For example, such a test can fail because it is run on a machine that is slower than usual. Aim for unit tests that fail if and only if the production code is broken. Rather than using Thread.sleep
in a unit test, refactor the production code to allow the injection of a mock object that can simulate the success or failure of the potentially long-running operation that must normally be waited for.
2.15 Loggers
Use loggers to create an info comment on your test cases. This will make it easier to view runtime exceptions that can happen when running your test cases. It is advisable to use log4j or any extensions and implementations it has such as SL4J
2.16 Maven or Gradle – Build tools to automate
My personal favourite is Maven. It has everything needed to build, compile and run test cases for your system. It has an extensive plugin repository backed up by some of the best open source developers. We have long gone past manual ant builds, we now have better and improved build tools and mechanism. We should use them at our disposal.
2.17 Test Case Coverage and report using surefire plugin
A surefire plugin is one way of creating an html page report of the test case coverage run. Highly advisable for developers to use this so that they can have a clear and concise metrics report of their individual or all their specific test case.
2.18 The 80% test coverage rule
Rule of thumb says, test coverage must be at least 80%! Projects need to hit this targets as much as possible! There are certain cases specifically for legacy systems that haven’t done any test case ever since they were started (again a no no!). For this, make sure that for all the extensions made by the project to the system, should at least increase the coverage proportionally to the actual change.
2.19 Mock your data!
There’s a ton of mocking api available to be used. Mocking is basically a way to create a shallow proxy object that can be used on your test cases.
2.20 Be creative with your test case!
There will be a lot of scenarios possible for your test cases and in some instances, you might be dealing with a service that needs to generate specific HTML templates, velocity, jasper or wsdl files. Developers need to be creative to account for these specific scenarios. It’s not just about testing your Java Service code and check if it will give you no exception. It should do what it supposed to do.
3. The General Tip: Keep tests small and fast
Executing every test for the entire system shouldn’t take hours. Indeed, developers will more consistently run tests that execute quickly. Without regularly running the full set of tests, it will be difficult to validate the entire system when changes are made. Errors will start to creep back in, and the benefits of unit testing will be lost. This means stress tests and load tests for single classes or small frameworks of classes shouldn’t be run as part of the unit test suite; they should be executed separately.
There’s a lot of resources available that identify best practices in creating JUnit Test case. It all comes really down to the discipline. Developers need to have that mindset: it’s not just all about the implementation, it is also about the proof of how stable, scalable and functionally working the code is.
4. Download the Eclipse project
This was an example of JUnit Best Practices.
You can download the full source code of this example here: junit-best-practices
Can we please forget suggesting to people to use one assertion per test? Sometimes you need to check the output of the unit/module under test which is complex enough so that you can only express the assertion with multiple calls. Both JUnit and e.g. AssertJ knows how to deal with this problem without losing information in the report.
I fully understand your point and admit that at times you will have a complex unit/module, and you end up with multiple assertions in one test, to make it one comprehensive test. That is but a simple fact of actual software-development. :) Still, please keep preaching to use only one assert per test! A unit/module that cannot be sensibly tested with only one assert per test is an indicator for a flaw within the system’s architecture. Refactoring and Redesigning are not always approved of, in which case you will have to drag along the unit/module as complex as it… Read more »