Clean Code Matters: Unit Testing [Part 1]

Posted by

Unit testing can be defined as testing the smallest unit of a system. The smallest unit of a system could be a function, a method, a subroutine, or even a property. There are multiple definitions of a unit, and we’ll see some of them in the next section. It’s important to note here that unit testing tries to isolate a unit from the rest of the system and tests for the proper functioning of that one unit alone. This helps in asserting that one small unit behaves the expected way for the inputs given, while removing any dependency on the other parts of the system. Breaking down a system to such multiple small units and testing them separately and individually helps in validating the functioning of the entire system.


What is a unit? 

Understanding what a unit is is very important to design the code and also write unit tests that can be relied upon. If we get the definition of a unit wrong and proceed with that wrong definition, our unit tests might fail to recognize bugs in the system. There are many definitions for a unit, each book on the subject might give a different definition. But the commonality is that a unit should be logically isolated (meaning there shouldn’t be any dependency on other parts of the system), and it should be a small isolated part of a system. 

It is generally accepted that a unit is the smallest piece of testable code in a program. But depending on the programming language, a unit could mean a method, a function, a subroutine, or an entire class. You can argue that an entire class is too vast to qualify as a unit, and you wouldn’t be entirely right or wrong. Along with being the smallest testable code, a unit should also perform an action on the inputs, or return a value based on certain criteria for it to be testable. If a unit is returning a constant value, there’s nothing to be tested. 

For example, consider the following Java code snippet:In this example, you can easily test if the method addNumbers() is returning the sum of the two numbers that are passed to it. At the same time, consider the following method:
This method isn’t performing any operation and is just returning a constant value, thereby leaving almost nothing to be tested. Instead of having this method return a constant value, we should define a constant for this, something like this:

A best practice while designing a method is to make sure it performs only one operation, and the name of the method is clearly telling us what that operation is. For example, look at the following method.

This is a very basic example to show what not to do. In this example, we are trying to validate the credentials. In case of valid credentials, along with passing a true, we are also saving the login to a DB. This method is performing more than one operation, and the name of the method doesn’t tell us anything about the saveLogin() operation. This is a clear violation of the Single Responsibility principle. In such cases, we will have to mock the saveLogin() method and test the credential validation part alone. This can be done, but isn’t the right approach to begin with. Unit testing becomes easier and pretty straightforward when the unit we are testing is following the Single Responsibility principle.  

Unit Testing Vs. Test Driven Development (TDD) 

There is a common misunderstanding that TDD is just unit testing, or that unit testing is nothing but TDD. But that’s not true. We’ve already seen that unit testing is the process of testing a small piece of code. But Test Driven Development is an approach to writing code. The TDD approach helps us in making sure we understand the problem domain properly and design the code in such a way that each aspect of the problem domain is addressed while also being well tested and bug-free. Actually, unit testing is a part of the TDD approach. 

There’s something called the red-green-refactor cycle that drives TDD. In this cycle, instead of jumping right into code development, we logically break down the code into small chunks, and then start by writing unit tests for these chunks. Now, with just the unit test and no code, the test will fail. This is the expectation. Next, we write enough code to turn that red, failing unit test to green, or to pass the unit test. Once the red unit test becomes green, we write the next unit test for the next test, which will again fail because the code for that unit test is not yet written. We refactor the code to turn this new red test to green. And this cycle continues until we have all the code we need for a feature. And by default, we’ll have all the unit tests we need to satisfy all test criteria. 

This way, we’re making sure all our code is unit tested, and we have covered all edge cases as well in the code so that there is no surprise firefighting in the future. As you can see, this approach helps us identify more test scenarios and edge cases when compared to writing the code first and then writing the unit tests as an afterthought. I have seen in my personal experience that teams that practice TDD properly have far fewer bugs compared to teams that don’t. And because of the way it’s designed, it’s easier to just run the tests multiple times whenever we change anything in the code. And every time all the tests are run and they all pass, we can be sure that everything works as expected.  

But this doesn’t in any way discount the fact that unit tests outside of TDD are not useful. Even in a legacy project when you bring in unit tests, you will observe that you’ll have to refactor the code for proper unit testing. This will in turn lead to better code quality and confidence on the refactored code. 

Unit Testing Vs. Integration Testing 

Integration testing is another form of testing which is often confused with unit testing. Actually, if we don’t understand how to properly mock parts of the code for unit testing, we might end up performing integration testing. 

As the name suggests, integration testing is the testing of the interface between two units of code or two systems. Here, the assumption is that the two units involved are already unit tested well and are expected to be behaving as they must. After that confidence is achieved, we move on to test whether the two units behave the way they are expected to when they start talking to each other.  

As already mentioned, integration testing comes after unit testing, but it is performed before system testing. Integration tests are slower to run when compared to unit tests. Because integration tests cater to a wider scope, it is usually harder to maintain than unit tests. And unlike unit tests where the developers write the tests along with the code, integration testing is usually performed by a different group of people who understand both the units in question in terms of the features they offer. And because this group of people don’t know the design of either of the units, it becomes difficult to identify bugs. And it doesn’t help that there are often many scenarios to cover in integration testing. Also, because integration testing takes a lot of time to complete, it is run less frequently than unit tests. 

But integration tests are as important as unit tests, and should be run frequently to make sure everything is in order. Both unit tests and integration tests should be performed vigorously and the results of each test should be given proper attention. 

Unit Testing Frameworks 

Each major programming language has multiple unit testing frameworks. In this section, we’ll see a few of them for a few programming languages. 

Jest 

Developed by Facebook, Jest is a JavaScript framework for unit testing code written in JavaScript. This is mostly used for apps written in React and React Native, which are also JavaScript frameworks developed by Facebook. But it can be used with all major JavaScript libraries and frameworks including AngularJS, NodeJS, VueJS, and others. Jest makes unit testing front-end applications easy. One best part about Jest is that it can be run without any kind of configuration, making it one of the easiest unit testing frameworks. As with most other JavaScript unit testing frameworks, Jest also features testing user interface elements by enabling browser rendering, which comes in handy in front-end application testing. 

JUnit 

JUnit is one of the most popular unit testing frameworks for Java, and it is also one of the most feature-rich unit testing frameworks out there. It is easy to learn so that even beginners can pick it up easily right from the beginning of their coding journey. JUnit can automatically run tests and check the test results automatically too using the assertions library. This makes it possible to integrate unit testing in the building process, so that a build fails whenever there’s a unit test failing. Along with color coding (green for passing tests and red for failing ones), JUnit also provides handy tools to generate reports that makes understating the test outcome easy. 

NUnit 

Similar to JUnit, the .NET programming language has NUnit for all your unit testing needs. NUnit comes with some advanced features such as using third party test runners, running tests in parallel or a separate process, and much more. These features make NUnit a very versatile unit testing platform for .NET applications. It even supports multiple platforms such as Silverlight and .NET Core for even greater customizability. 

There are numerous other unit testing frameworks, especially for JavaScript and front-end application testing, such as Mocha, Jasmine, Storybook, and others. Each tool comes with its own strengths and weaknesses. The best part is, we can use a combination of these for testing various aspects of our applications. And all of these have just one goal to achieve, release good quality and bug-free code so that the users have a good experience using our applications.

Next>>
Unit Testing Part 2: Examples, advantages and disadvantages

<<Previous
The first 3 articles in this series:
Clean Code Series 1: OOP Part 1
Clean Code Series 2: OOP Part 2
Clean Code Series 3: Clean, Readable Code

Leave a Reply

Your email address will not be published.