Image Blog Top Testing Tips for Discriminating Java Developers
January 21, 2015

Tips for Testing Java Code

Java Testing
Java Application Development

Testing Java code can be tricky, but it's a necessary component of any good application. In this blog, we look at high level tips for testing Java code, including the techniques, technologies, and best practices that can help you get the most out of your tests.

Back to top

Tips for Testing Java Code

We all write tests for our code don’t we? I have no doubt that the range of answers is from “Sure, but do you know how to avoid it?!” to “Absolutely positively yes! I love tests”, so I’m going to show you a few tips that should make writing your tests easier. That will help you write less fragile tests and ensure your applications are more robust.

At the same time, if your answer was “No, I don’t write tests”, hopefully these simple, yet effective techniques will show you the benefits of writing tests and you’ll see that writing a comprehensive and invaluable test suite is not as difficult as you may think.

How to write tests and what are the best practices for managing test suites are not new topics. We’ve discussed some aspects of these in the past, including the correct way to use integration tests in your build process and properly mocking the environment in unit tests.

Today, however, I want to run you through a series of tips that can help you organize your mental picture of how tests should operate both on a low and high level, from how to structure a single unit test to a higher plane of understanding mocking vs. spying and copy-pasting tests code around. Let's get started. 

Back to top

1. Find Patterns in Java Testing

As with most software development, looking at patterns is usually a good place to start. Whether you want to create objects via a factory, or want to separate the concerns of your web application into a Model, View and Controller, there’s usually a pattern in the background that’s helping out and governs your intuition about what’s happening. So, what should a typical test look like?

When writing tests, one of the most useful, yet incredibly simple patterns is the Arrange-Act-Assert pattern, or AAA.

The premise of this pattern is that all tests should follow the default layout. All necessary conditions and inputs to the system under test should be Arranged at the beginning of the test method. After arranging all the preconditions, we Act on the system under test by invoking a method, or checking some state of the system. Finally, we need to Assert that the system under test has generated the expected result.

Let’s take a look at a sample Java JUnit test that shows this pattern.


    @Test
    public void testAddition() {
    
        // Arrange
        Calculator calculator = new Calculator();
        
        // Act
        int result = calculator.add(1, 2);
        
        // Assert
        assertEquals("Calculator.add returns invalid result", 3, result);
    }


See how nicely the code flows? Arrange, Act, Assert pattern gives you instant understanding what is where in the test. Deviating from this pattern can easily lead to much messier code.

Back to top

2. The Law Of Demeter for Design

The Law of Demeter applies the principle of the least knowledge to software to promote loose coupling between units - which is always a design goal when developing software.

The Law of Demeter can be stated as a series of rules:

  • Within a method, an instance of a class can invoke other methods of the class;
  • Within a method, an instance can query its own data, but not the data’s data;
  • When a method takes parameters, the first level methods can be called on the parameters;
  • When a method instances local variables, the instance of the class can invoke methods on these local variables;
  • Don’t invoke methods on global objects. 

So, what does this mean in terms of testing? Well, this means that it’s much easier to test units within your application because of the loose coupling that the Law of Demeter promotes within your application. To see how this law can aid with testing, let’s take a look at a poorly defined class that disobeys these rules.

Consider the following Java class that we want to test:


public class Foo() {

	public Bar doSomething(Baz aParameter) {
		Bar bar = null;
		if (aParameter.getValue().isValid()) {
			aParameter.getThing().increment();
			bar = BarManager.getBar(new Thing());
		}
		return bar;
	}
}


If we tried to test this method, we would immediately come up with some problems due to the way that the method is defined.

The first difficulty we’d come across testing this method is that we call a static method - BarManager.getBar(). There’s no way for us to easily specify how this method will operate within the constraints of our unit test. Remember the Arrange, Act, Assert pattern we’ve mentioned before? Here, though, is no easy way to setup a BarManager before we act on the method by calling the doSomething() method. If the BarManager.getBar() method was non-static and we could pass a BarManager instance to the doSomething() method, it would be much easier to pass in sample values in the test suite and provide a much greater and predictable control over the flow of the method.

We can also see within this sample method, that we are calling chains of methods: aParameter.getValue().isValid() and aParameter().getThing().increment(). To test them, we clearly need to know what type of object aParameter.getValue() and aParameter.getThing() return, so we can mock the suitable values to use in our test.

If we were to do this, we’d have to have quite intimate knowledge of the objects that these methods return and our unit tests would start morphing into a big lump of unmaintainable brittle code.  We’d be breaking one of the basic rules of unit testing, which is to test individual units, not the details of the implementation of these.

I’m not saying unit testing can only test individual classes, however in most cases, it’s probably a good idea to think of classes as individual units. Sometimes however, two or more classes can be considered a unit.

I’ll leave it as an exercise for the reader, to fully refactor this method into something that’s easier to test. But for starters, we could pass the aParmater.getValue() object as an argument into the method. This would satisfy some of our laws and make the method more testable.

Back to top

3. Take Advantage of Java Testing Assertions

JUnit and TestNG are both excellent frameworks for writing application tests and they provide many different methods for asserting values within a test, for example to check if values are equal or different, or are null.

OK, so we’ve agreed that assertions are cool, let’s use them everywhere!  Hold on a moment there, aggressive use of asserts can make your tests brittle which results in you not maintaining them, and we know where that road leads to… untested and unstable code.

Consider the example Java test below:


@Test
public void testFoo {
    // Arrange
    Foo foo = new Foo();
    double result = …;

    // Act
    double value = foo.bar( 100.0 );

    // Assert
    assertEquals(value, result);
    assertNotNull( foo.getBar() );
    assertTrue( foo.isValid() );
}


This code looks fairly innocuous at the first glance. We’re following the AAA pattern and asserting that some things happen. What’s wrong?

First, we can see that the name of the test, testFoo, doesn’t really tell us anything about what the test is doing and doesn’t really match with any of the assertions that we’re checking.

Then if one of these assertions fails, can we really be sure what part of our system under test has failed? Is it our action, foo.bar(100.0) that has failed, or is it the foo.getBar() or foo.isValid() methods that have failed? There’s no way of telling without debugging into the test and trying to work out exactly what has happened.

Such a hassle defeats the purpose of unit tests where we want to have a reliable, robust set of tests that we can quickly run to establish the state of our application. If a test fails, and we have to launch the debugger to work out what has actually failed, we’re on a slippery slope.

It’s generally a good practice to have a minimum number of assertions, preferably just one, within a particular test so that we can ensure that tests are specific and target a single piece of functionality within our applications.

📋 Want more testing tips? Check out Performance Testing Your Website Under Pressure from our friends at BlazeMeter.

Back to top

4. Spies, Mocks & Stubs

Sometimes, it can be useful to spy on what your application does, or verify that certain methods are called with certain parameters a specific number of times. Sometimes we want to invoke our database layer, but want to mimic the responses that the database returns to us. All of this type of functionality can be achieved with the help of spies, mocks and stubs.

In Java, there are many different libraries that we can use for spying, stubbing and mocking such as Mockito, EasyMock and JMockit. So, what’s the difference between a Spy, a Mock and a Stub and when would we use them? A spy enables you to easily check that methods are invoked within your application with the correct parameters and can also record the parameters for later validation.  For example, if you have a loop within your code that invokes a method once each time through the loop, a spy can be used to verify that the method is called the correct number of times with the correct parameters. Spies are essentially specific types of stubs.

A stub is an object that provides certain stock responses when invoked by clients, i.e. they have pre-programmed responses to their inputs. Stubs are useful when you want to force a certain condition within a piece of code, for example if a database call fails and you wish to invoke database exception handling within your tests. Stubs are a specific case of mock objects.

A mock object provides all of the functionality of a stub, but also provides pre-programmed expectations. This is to say that a mock object much closer represents a real object and can perform different actions based upon what state is previously set. For example we could use a mock object to represent a security system that provides different access controls dependent upon the user that is logged into it. As far as our test is concerned, it would be communicating with a real security system and we would be able to exercise many different paths within our application.

Sometimes the term Test Double is used to reference any type of object such as those described above that we use to interact within our tests.

Generally, a spy offers the least functionality as its purpose is to capture whether methods are called and what parameters they are called with.

Stubs are the next level of test double as they allow the flow of systems under test to be specified by specifying pre-defined return values from method calls. A specific stub object can typically be used within many tests.

Finally, a mock object provides much more behavior than a stub. As such, it’s a good practice to develop specific stubs for specific tests otherwise the stub object starts to become as complex as the real object.

Back to top

5. Java Code Testing Can't be Too DRY

In software development, it’s usually a good practice to keep your applications DRY - Don’t Repeat Yourself.

In testing, this is not always the case. When writing software, it’s a good practice to refactor pieces of commonly used code out into separate methods that can then be invoked many times from within a code base. It makes sense, write some code once and then we only have to test it once. Additionally, if we only write a piece of code once, we can’t type it in incorrectly a second or third time giving us more errors. Be wary of copy and paste!

In 2006, Jay Fields coined the acronym DAMP, Descriptive And Meaningful Phrases when referring to well designed domain specific languages. Here’s the original post if you need to refresh your memory: DRY code, DAMP DSLs.

The theory behind DAMP is that a good domain specific language uses descriptive and meaningful phrases to increase the readability of the language and to reduce the learning and training time required to become productive.

Typically, many unit tests can be very similar to the previous tests in a suite, differing only slightly in the arrangement of the system under test.  It’s therefore natural for a software developer to refactor duplicated code from a unit test into a helper function.  It’s also natural to move instance variables to be static so that they are only declared once per test class - again removing duplication from tests.

Although the code will be “neater” after these types of refactoring, the unit tests will be harder to read as an individual story.  If a unit test is calling several other methods and using non-local variables, the flow of the unit test becomes less linear and it’s not as easy to understand the basic flow of the unit test.

Essentially, if we make our unit tests DRY, the complexity of the tests becomes greater and ironically, the maintenance of the tests can also get harder - the exact opposite of what making the tests DRY was attempting to do.  For unit tests, making them more DAMP than DRY can make them easier to read and maintain.

There’s no right or wrong answer on how much refactoring you should perform within your tests, but striking a balance between making tests too DRY or too DAMP will almost certainly make your tests easier to maintain.

Back to top

Final Thoughts on Testing Java Code

In this article, I’ve introduced five basic principles that should aid you when writing unit tests for your applications. Hopefully you’ve enjoyed the principles we discussed and can see how they could potentially make you love writing unit tests. Yes, I said “love” because I believe writing unit tests is fundamental to quality software. Quality software means happy users, and happy users means happy developers.

Looking for ways to improve efficiency in your Java development practice? Try JRebel. By eliminating rebuilds and redeploys, you could save a month of development time annually. See for yourself during your 14-day free trial. 

Try free

 

 

 

 

Back to top