Testable Code Writing Essentials

Ersen
Odeal-Tech
Published in
5 min readDec 14, 2020

TDD technique enforces developers to build the code upon tests.Thereby, only test scenarios are written.So, if we follow TDD technique, we will not write any single line of code which is not testable.

Testable code means we have a code for which we can write tests easily. Tests are vital indeed but we don’t have to apply TDD for every time.That means sometimes we need to write code and add tests afterwards.Not matter when, we should know if our code is testable or not.

Actually, the rule is simple : a well OOP design provides a well testable code.

Fortunately, we can describe a well OOP design easily:

  1. Loose Couple
  2. High Cohesive

While coupling refers to how related or dependent two classes/modules are toward each other, cohesion concentrates on what the class or module can do.

High cohesion means that the class is focused on what it should be doing, i.e. only methods relating to the intention of the class. For low coupled classes, changing something major in one class should not affect the other.The tool we are given to achieve these is S.O.L.I.D principles.

In short, the five S.O.L.I.D. principles are the following.

As we can see, we need to apply SOLID principles to build an object oriented code.And also testable code at the same time.

So, let’s deep dive some guidelines about developing testable codebase:

  1. Use Dependency Injection

Especially for unit testing, Dependency Injection is a must.We cannot mock our dependencies without Dependency Injection.We should avoid object creation with application logic.Let me show you with an example:

import java.util.Random;public class RandomService {  public int generateRandom(int incr, int bound) {
final int password = incr + new Random().nextInt(bound);
return password;
}
}

Above code provides a function.This function generates a random number and increments it with a given value.In this case, we have no chance to write a unit test for generateRandom function.Because there is no way of mocking random api.This design is highly coupled.

Let’s refactor this class by using dependency injection:

import java.util.Random;public class RandomService {  private Random random;  public RandomService(final Random random) {
this.random = random;
}
public int generateRandom(int incr, int bound) {
final int password = incr + random.nextInt(bound);
return password;
}
}

Yes, in this case we have a way of mocking Random API such as:

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Random;@ExtendWith(MockitoExtension.class)
public class RandomServiceTest {
@Mock
private Random random;
private RandomService randomService; @BeforeEach
public void setup() {
this.randomService = new RandomService(random);
}
@Test
public void test() {
Mockito.when(random.nextInt(900000)).thenReturn(123456);
int result = deleteMe.generateRandom(100000, 900000);
Assertions.assertThat(result).isEqualTo(223456);
}
}

Don’t mix object creation with application logic

2. Make sure your code has seams

From the definition on Working Effectively with Legacy Code by Michael Feathers,A seam is a place where you can alter behaviour in your program without editing that place”. Having seams is required in order to unit test code as you can replace behaviour with test doubles. Following the principles stated above will help with that as well as practising Test-Driven-Development, as unit tests can help with the design of your APIs.

import java.util.Calendar;public class DateUtil {  public String getTimeOfDay() {
Calendar now = Calendar.getInstance();
int hour = now.get(Calendar.HOUR);

if (hour < 6) {
return “Night”;
}
if (hour >= 6 && hour < 12) {
return “Morning”;
}
if (hour >= 12 && hour < 18) {
return “Afternoon”;
}

return “Evening”;
}
}

Ok, what’s wrong with this code?

Well:

  • Tightly coupled with concrete data source
  • Violates SRP
  • Hard to predict and maintain
  • Lies about the information required to get its job done

It’s hard to test such a non deterministic code.

What about refactored below code:

public String getTimeOfDay(Calendar calendar) {
int hour = calendar.get(Calendar.HOUR);
if (hour < 6) {
return “Night”;
}
if (hour >= 6 && hour < 12) {
return “Morning”;
}
if (hour >= 12 && hour < 18) {
return “Afternoon”;
}
return “Evening”;
}

Now, instead of secretly looking for this information by itself, the method requires the caller to provide a Calendar argument. Now we have a deterministic method which is great for the unit testing perspective.

I want to give another example with the same concept.Look at the below code:

void calculate() {
Shape shape = new Triangle(4,6);
System.out.println(shape.calculateArea());
shape = new Square(5);
System.out.println(shape.calculateArea());
}

The same problem again! The class of the shape is decided when the object is created, and we can’t change it without modifying the method.In this case calculateArea is not a seam.

Let’s make calculateArea method a seam by refactoring with the same technique:

void calculate(Shape shape) {
System.out.println(shape.calculateArea());
}

3. Avoid to use global state:

Global states are very dangerous for oop programmers. Most of the race conditions happen because of them. They bring side effects.But manipulating global states also causes non deterministic tests since tests are influenced by each other.So, don’t be afraid of passing parameter.Try to make more immutable designs for the sake of security and tests of course.

4. Avoid static methods

Static keyword finishes object oriented code, initiates class oriented code.But sometimes we have to use deterministic static functions such as static Math methods(Math.min, Math.abs etc). These kinds of usages may not hurt unit testing.But methods like System.currentTimeMillis() can make the unit test difficult. Although there are some useful libraries for static method mocking, like PowerMock, we can also benefit from dependency injection. If you instead have a TimeSource interface, you can dependency-inject an implementation that uses System.currentTimeMillis() in your production code and use a fake implementation in your test code, which might help you create better assertions in your tests.

The TimeSource interface allows you to define various implementations of a fake system clock:

public interface TimeSource { 
long currentTimeMillis();
}

This implementation can be used in production code:

public final class TimeSrc implements TimeSource {

@Override
public long currentTimeMillis() {
return System.currentTimeMillis() + ONE_DAY;
}
}

And this one for test purposes:

public final class TimeSrc implements TimeSource {
@Override
public long currentTimeMillis() {
return System.currentTimeMillis() + ONE_DAY;
}
}

5. Favour composition over inheritance

Unit testing is easy in composition. Because we know all methods we are using from another class. We can mock it up for testing. But in inheritance, we depend heavily on superclass and don’t know what all methods of superclass will be used. So we will have to test all the methods of the superclass. This is extra work and we need to do it unnecessarily because of inheritance.

--

--