Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Rearchitect Your Code Using Test-Driven Development

DZone 's Guide to

Rearchitect Your Code Using Test-Driven Development

Test-Driven Development makes it possible to construct more robust code with the revelation of previously hidden bugs.

· DevOps Zone ·
Free Resource

"Write your unit tests before your code," an adage that every developer knows by heart, in theory, but seldom practices it in reality.

Test-Driven Development (TDD) is a novel concept that instructs developers to write unit test cases before writing even a single line of code.

Robert C. Martin, or Uncle Bob, as he is famously known by many, proposed three laws of test-driven development that every developer who practices it (or at least thinks he does) should follow.

Law 1: You are not allowed to write any production code unless it is to make a failing unit test pass.

Law 2: You are not allowed to write any more of a unit test than is sufficient to fail, and compilation failures are failures.

Law 3: You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

These three laws, in my opinion, are very comprehensive and are a wholesome guide for anybody who is starting his/her journey with TDD.

But the question is, are unit tests really that important? What benefits do they actually bring to our code? Is following TDD the only way to go about writing unit tests?

Well, the answer to the first question is rather simple: units tests are really, really important,

Unit tests can help you achieve a greater sense of confidence on your code and provide you with the information you need to make any change to existing (running) production code and ensure you don't break anything or introduce any new bug inadvertently.

But TDD is not the only way to achieve these benefits; writing your unit tests after writing your code can pretty much provide you with the same confidence.

However, TDD gives you something much more advantageous than just code confidence,;it gives you a way to design your application better, and the amazing thing is you don't have to do anything extra for it. By following the three laws of TDD, your final application will be elegantly designed and robustly tested.

Let's look at an example to understand how it works.

Suppose I am planning to write code for binary search (For those who are unfamiliar with binary search, click here).

binarySearch( arr[],  left,  right,  x) 
{ 
    if (right >= left) { 
       mid = (left + right) / 2; 
        if (arr[mid] == x) 
            return mid; 
        if (arr[mid] > x) 
            return binarySearch(arr, left, mid - 1, x); 
        return binarySearch(arr, mid + 1, right, x); 
    } 
    return -1; 
} 


Binary search is a simple search algorithm that has its time complexity as O(n).

Many of you may have cringed by looking at this code, but the error is intentional.

The basic principle of binary search is to divide the search space into halves until the time either the search space is empty or the element is found, and the statement mid = (left + right) / 2 , plays an important role in identifying the direction of the search.

Now if I started writing unit tests for this function, at first, I would throw in some generic tests, having an array of 10 or 20 elements and ensure that my code works as it should. After that, I would like to test corner cases and see if my code breaks.

The point of concern here is the statement: mid = (left + right) / 2

What is the problem with this statement? If leftand right get sufficiently large, their sum would overflow and become negative, and division by 2 again would remain negative, so this statement would return a negative number as a mean of two positive numbers, which is mathematically impossible. This line would fail rather miserably in its intention to find the mean of 2 numbers.

If this gets shipped it could unleash chaos, and everyone would come searching for the culprit asking hy didn't they test their code? We are supposed to test everything before we push, but then to test and identify the bug we just saw, we would need very very big arrays, arrays that are not feasible to be created once, let alone every time you build your code. However, tests like these can slow down development, so how should we tackle such problems?

The answer is TDD.

TDD To the Rescue

If we followed the Laws of Test Driven Development, to their word, we would be at a stage, where before writing mid = (left + right) / 2 ,we would have to write a test for calculating the mid value, but how do you write a test for half-cooked function? The answer is you don't; you extract that logic statement to a separate function and then write the test for it.

Let's call it calculateMean. Now our code would look something like :

calculateMean(first, second){
  return (first+second)/2;
}   

binarySearch( arr[],  left,  right,  x) 
{ 
    if (right >= left) { 
       mid = (left + right) / 2; 
        if (arr[mid] == x) 
            return mid; 
        if (arr[mid] > x) 
            return binarySearch(arr, left, mid - 1, x); 
        return binarySearch(arr, mid + 1, right, x); 
    } 
    return -1; 
} 


To test this function, we just need to create two big numbers, which is way less expensive than creating two big arrays. Testing the aforementioned function with two very large values would make the bug that was mysteriously hidden clear to us, and we would quickly jump and fix it.

After this our code would look like:

calculateMean(first, second){
return first + (second - first)/2;
} 

binarySearch( arr[], left, right, x) 
{ 
if (right >= left) { 
mid = (left + right) / 2; 
if (arr[mid] == x) 
return mid; 
if (arr[mid] > x) 
return binarySearch(arr, left, mid - 1, x); 
return binarySearch(arr, mid + 1, right, x); 
} 
return -1; 
} 


And done, we have solved an undetected bug, which by the conventional method of testing would rarely be caught.

Just to reiterate, never did we say that we are trying to improve the design of binary search; all we were trying to do is write unit tests following the three laws of TDD and what we have in the end is an elegantly designed and robustly tested piece of code, just as promised.

Topics:
test driven developement ,uncle bob ,binary search ,devops ,bug detection

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}