3 concrete steps that will make your code unit testable

 

Arnold says write tests !
Arnold says write tests !

This post explains three pragmatic techniques which will make your code more unit-testable.

I will list them down first and then go on to explain them in more detail.

  1. Write logic in pure functions.
  2. Separate out algorithmic code from coordination oriented code.
  3. Understand your aggregate roots.

 

Write logic in pure functions

A pure function has two useful characteristics.

It has no side effects. The function does not change any variables or the data of any type outside of the function.

It is consistent. Given the same set of input data, it will always return the same output value.

pure functions can be written as static functions because they operate only on the arguments passed in and do not have any side effects.

Below is a code snippet in which logic is refactored out into a pure function, the instance method delegates to the pure function. This simple refactoring lets you test the proratedDuration() static function without having to worry about dependencies.

 

Separate out algorithmic code from coordination oriented code

At a high level split the code you write into 2 roles

Code that does work (algorithms)

Code that coordinates work (coordinators)

Once you separate out algorithmic code of dependencies you’ll find 3 things happen to that code:

  • It becomes easier to unit test
  • It becomes more reusable
  • Its complexity is reduced

Below is a code snippet in which both the roles are mixed up. Such code is hard to test because of the additional dependencies that need to be mocked out to be able to test the actual logic. In the below example we need to mock out wsCall() and dbSave() methods before attempting to unit test the core business logic (Assume that’s line 4).

Now consider the below refactored code snippet which separates out the two roles.

In the above snippet you can independently test the theLogic() method without needing to mock out any dependencies.

As you might have already guessed this is closely related to Rule 1 of writing logic within pure functions, both rules lend themselves to each other.

Understand your aggregate roots.

SONY DSC

Lets begin with a short definition for aggregate roots.

A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it’s useful to treat the order (together with its line items) as a single aggregate.

The key to writing to testable code is to eliminate unnecessary dependencies. Designing aggregates with incorrect boundaries leads to bloated aggregates which are difficult to unit-test.

Take the example of a typical Order class, the Order class has a Product dependency which means all unit-tests of the Order class either should know how to create a Product instance or know how to mock it.


public class Order {
@ManyToOne
private Product product;
}

This kind of code ends where aggregates reference other aggregates directly ends up in a highly coupled and endlessly interconnected web of classes.

Consider the alternative of referencing external aggregates via reference.


public class Order {
@Embeddable
private ProductId productId;
}

Here the ProuctId class is a lighter dependency and much easier to set-up in unit-tests. The subject of designing aggregate roots is discussed in greater here.

 

These three tips have helped me improve the testability of my code and I hope you will find it useful too. If you think it has been worth your time, please do to share it.


References