Stop Writing Code That You Can’t Test

This post was written by Salaikumar.

Hello There!

So, we at GeekyMinds were discussing on what to learn next along with you. We just had a look at our past and understood how far we have come.

We started from our childhood and thought of all the ways that helped us evolve. There was one common question that kept coming umpteen times in the discussion.

How do we evaluate our growth?

I understand there was no generic answer to that question. But in the world of Bits and Bytes, there is!

Testing. So, we decided to learn how to test our code and focus on the best practice of TDD ( Test Driven Development ).

We will be writing a series of blogs on Testing and TDD. Here is the first blog of the series:

WELCOME TO THE WORLD OF TESTING

Let’s assume you write a program that simulates a matrix. A simple one-dimensional matrix.

Have you ever been like this, staring at your monitor?

Testing_img_1

Or, your program seems to be working as expected for your inputs but not for the one your friend gave?

If so, welcome to reality. This is going to be part and parcel of your work if you don’t test your code.

What is Testing?
Alright, Don’t start yawning now! Testing is often considered a boring part of programming. Let’s try to understand the importance of it and see few examples of it. Yup! No definitions copied from Wikipedia!

Let’s get started with an example:

package net.geekyminds.blog.testing;

/**
 * A simple one dimensional matrix
 */
public class OneDimensionalMatrix {

    private int matrixSize;
    private int[] matrix;
    private int currentIndex;

    public OneDimensionalMatrix(int matrixSize) {
        this.matrixSize = matrixSize;
    }

    public void add(int element){

    }

    public void remove(int indexOfElementToRemove){

    }

    public void insert(int element, int index){

    }

    public void reset(){

    }
}

That’s a class with just its API.

Let’s go ahead and do the first round of straightforward implementation for those methods:

package net.geekyminds.blog.testing;

/**
 * A simple one dimensional matrix
 * First Iteration of OneDimensional Matrix
 */
public class OneDimensionalMatrix {

    private int matrixSize;
    private int[] matrix;
    private int currentIndex;

    public OneDimensionalMatrix(int matrixSize) {
        this.matrixSize = matrixSize;
        this.matrix = new int[matrixSize];
        currentIndex=0;
    }

    /**
     * Adds the given element into the matrix
     * @param element
     */
    public void add(int element){
        matrix[currentIndex++]=element;

    }

    /**
     * Removes the element at the given index
     * ie, sets it to -1
     * @param indexOfElementToRemove
     */
    public void remove(int indexOfElementToRemove){
        matrix[indexOfElementToRemove] = -1;
    }

    /**
     * Inserts the given element to the given index
     * @param element
     * @param index
     */
    public void insert(int element, int index){
        matrix[index]=element;
    }


    public void reset(){

    }
}

So, now we have some code to play around. Before you take shots at me, note that we’re just in our first round.

Let’s see our methods one by one:

    /**
     * Inserts the given element to the given index
     * @param element
     * @param index
     */
    public void insert(int element, int index){
        matrix[index]=element;
    }

Our insert() method takes an element and adds it to our matrix. Simple, right?

Let’s test it. Assume we have declared matrix size as 3.

Our insert method takes a parameter named index. So, the method will work as long as the index < size of the matrix

But, are we doing that check? When will we come to know this?

So, here comes the role of test cases.

What is a Test Case?

A Test case evaluates your code for a normal condition against input/set of inputs.

The fundamental and vital part of testing is this:

Identifying all the possible set of inputs for your code, including the ones that would break your code

It’s the fundamental part of your testing. Define all the Test cases and ensure your code clears them. Define the negative test cases for which your code breaks.

Our next learning is,

Evaluate your input before using it

Always check if your input is meeting the normal conditions before you begin to process it.

So, our insert method is not perfect. Before we re-write it, let’s look at other methods and see what else is wrong.

    /**
     * Adds the given element into the matrix
     * @param element
     */
    public void add(int element){
        matrix[currentIndex++]=element;

    }

We are good here as long as there is space to add a new element. If we try to add a 4th element, our code will not work then.

And it’s the same scenario for the remove() method. We didn’t check the index of the element before using it. It may or may not fall under our matrix size.

Wait, What if it is a -1?
Yup!, We didn’t expect that, or handle that either

/**
     * Removes the element at the given index
     * ie, sets it to -1
     * @param indexOfElementToRemove
     */
    public void remove(int indexOfElementToRemove){
        matrix[indexOfElementToRemove] = -1;
    }

So,

  • Think of all possible set of inputs. Think of ranges Ex. Positive integer values, negative integer values,
  • Always evaluate the input data before using it
  • Throw proper information on your messages to user and exceptions. (This will reduce the 500 errors thrown by our codebase in future )

That’s some of our learnings out here. Now you have some basic understanding of how to test your code, let’s rewrite the class and all possible test cases to it.

package net.geekyminds.blog.testing;

/** 
  * A simple one dimensional matrix 
*/

public class OneDimensionalMatrix {
  private int matrixSize;
  private int[] matrix;
  private int currentIndex; 
  /**     
    * Parameterized Constructor     
    * Takes the size of matrix as input 
    * @param matrixSize     
  */ 
  public OneDimensionalMatrix(int matrixSize) {        
    /**         
      * Pre checks on input         
    */ 
    if (matrixSize <= 0) {
      throw new RuntimeException("Matrix size should be greater than zero");
    }        
    this.matrixSize = matrixSize;
    this.matrix = new int[matrixSize];
    currentIndex=0;
  }    
  /**     
    * Adds the given element into the matrix     
    * @param element     
  */    
  public void add(int element) {        
    /**         
      * Pre check         
    */        
    if (currentIndex == matrixSize) {
      throw new RuntimeException("Element cannot added. Matrix is full"); 
    }
    matrix[currentIndex++]=element;
  }    
  /**     
    * Removes the element at the given index     
    * ie, sets it to -1     
    * @param indexOfElementToRemove     
  */    
  public void remove(int indexOfElementToRemove) {        
    /*         
      * Evaluate the input before using it         
    */        
    if ( indexOfElementToRemove <0 || indexOfElementToRemove >= matrixSize) {            
      throw new RuntimeException("Given Index for removing element is invalid");        
    }        
    matrix[indexOfElementToRemove] = -1;    
  }    
  /**     
    * Inserts the given element to the given index     
    * @param element     
    * @param index     
  */    
  public void insert(int element, int index) {        
    /*         
      * Evaluate the input before using it         
    */        
    if ( index <0 || index >= matrixSize) {            
      throw new RuntimeException("Given Index for inserting element is invalid");        
    }        
    matrix[index]=element;    
  }    
  public void printMatrix() {        
    System.out.println("\n----------------------------\n");
    for (int i : matrix)             
      System.out.print(i +" ");        
    System.out.println("\n----------------------------\n");    
  }    

  /**     
    * Main Method.     
    * @param args     
  */    
  public static void main(String[] args) {        
    OneDimensionalMatrix oneDMatrix = new OneDimensionalMatrix(3);       
    /**         
      * Test method - Add         
      * Scenario : Positive, element added         
    */          
    oneDMatrix.add(3);          
    oneDMatrix.add(4);          
    oneDMatrix.add(5);          

    // Print the array and check it         
    oneDMatrix.printMatrix();        

    /**         
      * Test method - Add         
      * Scenario : Exception, element not added         
    */

    //oneDMatrix.add(6);  Will throw an exception        

    /**         
      * Test method - Insert         
      * Scenario - Positive , element inserted         
    */          
    oneDMatrix.insert(7,1);          
    oneDMatrix.printMatrix();        

    /**         
      * Test method - Insert         
      * Scenario - Exception , element not inserted         
    *
    //oneDMatrix.insert(1,7);
    //oneDMatrix.printMatrix();        

    /**         
      * Test method - Insert         
      * Scenario - Exception , element not inserted, Negative value for index         
    */
    //oneDMatrix.insert(1,-7);
    //oneDMatrix.printMatrix();        

    /**         
      * Test method - remove         
      * Scenario - Positive , element is set to -1 at given index         
    */        
    oneDMatrix.remove(2);        
    oneDMatrix.printMatrix();        

    /**         
      * Test method - remove         
      * Scenario - Exception         
    */
    //oneDMatrix.remove(-2);
    //oneDMatrix.printMatrix();        

    /**         
      * Test method - remove         
      * Scenario - Exception         
    */
    //oneDMatrix.remove(77);         
    //oneDMatrix.printMatrix();    
  }
}

Now, our code looks cleaner and have all those tests for the method in it. What we have done is called Unit Testing.

We have started with testing our code. But this printing the matrix and checking the results, commenting out the exception test cases, running the whole test cases all the time is a pain, right?

In our next blog, let’s see how easy is it for testing using test frameworks. Ah! We will see colors of testing as well.

Until then, Bye. Happy Testing!