loading...
Cover image for Testable Code 101

Testable Code 101

achimoraites profile image Achilles Moraites ・3 min read

What

Testable code is code that is easy to test.
Having testable code improves our codebase making it easier to add features, maintain or even scale it.

Why?

Testable code is an investment that will benefit your team and project in various ways, it can even make your grumpy project manager happy.

  • Testable code is more organized and will put us in the process of splitting our code's functionality to small, manageable and reusable components leading to more robust designs.
  • Refactoring your code is easier and you can verify on the spot if something is wrong thus saving you precious time and effort.
  • Writing your tests is faster and simpler.

Testable code has the following properties:

  • Controllability : Is the extend of our ability to execute the code under test with the states and parameters we care about.
  • Observability: The behaviors of our code that can be verified.
  • Isolateability: Failures can be isolated from each other.
  • Automatability: How able we are to automate our tests.

How

Examples of code being improved

Controllability:

Our Function checks is it is Monday: if it is Monday returns true otherwise false.

This code is not testable : we have no control over the date object.

The CONTROLABILITY attribute is missing!

This function will return true once a week and is not reliable for testing!!

function isMonday():boolean {
  let date = new Date();
  return date.getDay() === 1;
}

Now we have refactored our code to be more controllable : we now pass a date object.

We can create reliable test cases for this function because we have control over the date object used in the function.

function isMonday(date:Date):boolean {
    return date.getDay() === 1;
}

Observabillity:

The setName method is not observable, so we can't verify that the name is assigned.

We have made the name field public and this is a BAD IDEA to be able to test our method

class Student{
    public name: string = "";

     setName(name:string){
         this.name = name;
     }

}

So in our test suite we have something like:

 let student = new Student().setName('Patrick');
 expect(student.name).to.equal('Patrick');

we use the name field. There has to be a better way!

We refactored our code and now we can verify that the name is assigned while our name field remains private!

class Student{
    private name: string = "";

     setName(name:string):string{
         this.name = name;
         return this.name;
     }
}

Now we can test the method like that:

 let student = new Student();
 expect(student.setName('Patrick')).to.equal('Patrick');

Isoletabillity:

Isoletabillity can be achieved by splitting large methods to smaller components enabling us to better trace where a fault is located.

If you have a method of 80+ lines of code there are various things that are going on in there.
So it's not easy to find where the fault is located.

If we split the functionality inside the method using helper methods we can better test and understand where things gone bad.

Note here our enrollStudent method: if something fails in there can be a fault in setting the school functionallity , setName or Both. This method has no isoletabillity!

class Student{
    private name: string = "";

     setName(name:string):string{
         this.name = name;
         // 20 lines of functionallity
         return this.name;
     }




     enrollStudent(name:string,schoolID:number){
         this.setName(name);
        // set the school functionallity
        // lots of things here
        // like 30 lines of code
     }
}

Refactoring our code now our method is using helper methods to achieve the same functionality but now we can better test the method and it's helpers to be able to effectively trace the fault.

class Student{
    private name: string = "";

     setName(name:string):string{
         this.name = name;
         // 20 lines of functionallity
         return this.name;
     }


     setSchool(id:number){
         // lots of things here
         // 30 lines of code
     }


     enrollStudent(name:string,schoolID:number) {
         this.setName(name);
         this.setSchool(schoolID);
     }
}

Automatabillity

The goal is to be able to automate testing our functionality, to strive for code that can be tested and verified programmatically.

Assume we are developing a weather application , instead of relying on the actual api and having delays , network errors and other things out of our control, we can build our own mock api to simulate the functionality and to be able to better automate the testing process.

Recap

Writing our code to be testable makes it well organized, modular and loosely coupled.

This naturally leads to software that is clean and easier to maintain.

This article has been published on linkedin

Photo by Pixabay from Pexels

Discussion

pic
Editor guide