Updated March 13, 2023
What is Unit Testing?
Unit Testing is used to test specific modules of source code, in turn, to validate the correctness of the code, code coverage, implementation and maintenance of coding standards, verification of the functionality covered by the piece of code. This testing is traditionally organized and executed by the Developers themselves in the development environment before merging the code with other existing functional modules of code. Bypassing the unit testing process may lead to increased defect count, as the code itself can be faulty.
Types of Unit Testing
Given below are the types of unit testing:
1. Manual Testing
Manual testing of code requires the developer to manually debug each line of the code and test it for accuracy. It may require a step-by-step instruction set as well if the functionality is complex.
2. Automated Testing
In automation testing, the developer writes code to test code. This is generally assisted through Unit Test frameworks that are not deployed in production. Other times, a developer may choose to write test code without the framework and manually comment it out before deployment.
Manual Testing obviously seems time-consuming for most cases. But for some cases, when writing automated test cases to cover each and every scenario is not possible, manual is often the preferred method.
Why is Unit Testing Important?
To understand the importance of unit testing, we need to look at the broader picture. It is a part of the Software Development Life Cycle.
Let us briefly see the other parts to get a better understanding of the role of Unit Testing.
The above image is a simple illustration of a normal software development life cycle and the testing processes involved with it. Needless to say, depending on the structure of the project, the whole process varies with the addition and removal of certain components.
The testing process, however, most certainly involves four types as described below:
- Unit Testing: The elementary level of the entire testing process. This is performed by the developer of the component or any of his peers. In the latter case, it is often termed Peer Testing in the software world.
- Integration Testing: Testing the unit component with its immediate parent module. The goal is to check if the unit component integrates well with the other components and has not caused the malfunctioning of any other component.
- Systems Testing: Testing the entire system when the unit component is placed at its position.
- Acceptance Testing: Usually done by business/clients, it checks whether the outcome aligns with the functionality expected by the end-user.
Thus, one can very well see that all the testing processes rely on the elementary level of testing. If the elementary level of testing is not done, all other testings may result in futile.
Now let’s say you have a code that has two parts:
- Calculate compound interest.
- Add the interest to the principal amount and calculate the maturity benefit.
Let’s assume you did not test units any of these components and proceeded directly to system testing. A bug arises in system testing that the maturity value is incorrect. Now which part of the code has an error?
- It can be in the calculation of interest.
- It can be in applying the compounding logic.
- It can be in addition to interest to the principal amount.
See how it increases the effort now. All of this could have been avoided if both the code components had been unit tested.
Why is Unit Testing Important?
- It fixes bugs early in the development stage only. This saves a lot of time, effort, and cost. Imagine if there were no unit tests conducted, the code would go to and from the quality assurance team for very simple issues.
- Good unit tests also serve the purpose of detailed documentation. When a developer writes unit test cases, he is inadvertently writing the expected functionality of the code. This is simply nothing but documentation that explains the working of the code.
- It makes it easy to modify and maintain code. Once you have made any changes to the code, run the tests again, and viola, all the defects are caught with no hassles.
- It also enforces modularity. Unit tests are run on individual components, which means that the code needs to be as granular as possible. This ensures that the code is aptly divided into modules.
The Other Side of the Coin
It has some disadvantages too. Although the advantages weigh over the disadvantages, and it is always recommended to unit test your code, yet it also makes sense to know both the faces of the same coin.
- Unit Testing, how-so-ever thorough, can sometimes fail to catch all the errors in the most trivial code as well. It is simply not possible to evaluate all the execution paths. Thus, unit tests are often straightforward, happy-path, and negative scenarios.
- It requires a developer to think out of the box and try to break his code. This is often difficult as the perception of a developer gets biased towards the code.
Unit Testing Tools
There are several tools in the industry to assist with automated unit test cases. As is the purpose, they make writing and executing unit test cases easier for the developer. There is a world of unit testing frameworks at the disbursal of developers.
Some of the most popular and widely used tools are listed below:
- JUnit: JUnit is a free-to-use testing tool for Java. It is automatically included in many project templates available with various IDEs for Java development. What makes JUnit special is that it tests the data first and then tests the code after inserting the data in it. It also provides assertions to identify the test methods.
- NUnit: NUnit is to .Net as JUnit is to Java. It has all the salient features of JUnit, but for development in the .Net programming language. It also supports running the tests in parallel.
- PHPUnit: Similar to JUnit and NUnit, PHPUnit is a tool for PHP developers. It also supports all the elementary features of a good testing tool.
- XUnit: Another framework that is more generic than its counterparts is XUnit. It supports multiple languages like C++, C#, ASP.Net, etc. It also boasts similar features to that of other tools available in the market.
- Jtest: Parasoft Jtest is a third-party plugin that leverages open-source frameworks such as JUnit and adds one-click solutions to make life easier. With Jtest, you can automatically generate test codes for your code with just a few clicks. By automating these tasks, the developer is free to work on the business logic of the test cases.
- QUnit: A very popular JavaScript unit testing framework. It can test JavaScript code both on the client-side as well as server-side.
- Jasmine: Another very widely used testing tool for JavaScript frameworks. It has major community support for Angular, React, etc.
- JMockIt: JMockIt is an open-source tool that also supports mocking the API calls with recording and verification syntax.
Unit Test Case Example
A very basic requirement of any unit test case is the code to be tested. Let us assume we have a function that validates whether the phone numbers are correct (in terms of format) or not. Depending on the geographic location, this criterion may also vary. So, we will not emphasize the criteria. Rather we will focus on the unit test case.
Code:
public class PhoneValidator
{
public bool IsPhoneValid(string phone)
{
/* write some code to verify if the phone is valid or not. return true, if the phone is valid. return false, if invalid. */
}
}
Now we need to test this piece of code.
We can either test it manually by inserting various values and verifying the output. This may seem easy at the first look but will be a repeated task if any change is made to the code.
Alternatively, we can write a unit test case that can serve as my validator as long as the business logic remains the same. The unit test case won’t change even if we change the code. So, let’s write a unit test case for the above code.
Code:
public void TestPhoneValidator()
{
string validPhone = "(123) 456-7890";
string invalidPhone = "123 45"
PhoneValidator validator = new PhoneValidator();
Assert.IsTrue(validator.IsPhoneValid(valid phone));
Assert.IsFalse(validator.IsPhoneValid(invalidPhone));
}
So how does the above unit test code work? Notice the two Assert statements. They make sure that the test passes only if the two lines receive true and false from the respective IsPhoneValid function calls.
You would ask what are the benefits of writing this test case? Well, if you have thousands of phone numbers to validate in any real-world scenario, you need not manually verify each time the debugger hits the code. Simply call the test code thousands of times, and it will tell you which tests passed and which failed. Now you only need to inspect the failed ones.
Unit Testing Tips
- Always use a tool or framework supporting your language. Tools make it easy to develop unit test cases. You may end up putting in extra effort otherwise.
- Although it is recommended for everything, sometimes it is convenient to skip codes that are simple and do not directly impact the behavior of the system. For example, getter and setter codes can be less focused on.
- Do not ever skip codes that directly impact the system or are crucial to the business logic implementation.
- Use test data that resembles production data.
- Isolate your code. If your code depends on data from the database, do not write a test case to call the database and get values. Instead, create an interface and mock the API and database calls.
Before fixing a bug arising out of unit testing, write the test case that exposes the defect.
There are three reasons for doing so:
- You will be able to catch the regression defects arising out of your fix.
- Your test case is now more comprehensive.
- Often a developer is too lazy to update his test cases once written.
In addition to writing test cases that verify the business logic, write cases that test the performance of your code as well. Particularly when codes involve looping, the performance is the most impacted area.
Things to Remember
Given below are the things to remember:
1. Unit Test cases should be independent of:
- The code to be tested: Any change in the code should not require a change in the unit test case unless the business logic itself changes. For example, if the logic now demands that a valid phone number should always start with ‘+,’ then the unit test case needs to be changed, otherwise not.
- The other code: There shouldn’t be any interaction or dependency with any other piece of code or database value or any such thing. A unit should be isolated when being tested.
2. Follow clear and consistent naming conventions for your test cases. It makes it easier to track the scenarios. You can also use version control tools to keep track of your test cases.
3. Never pass your code to the next phase until it has been done, bugs are fixed, and retested.
4. Most importantly, make it a habit. This is a coding practice that needs to be inculcated. The more you code without unit testing, the more error-prone your code is.
A career in Unit Testing:
Although unit testing is no field as a whole, yet it is an additional arrow in your quiver. It is a good coding practice, and when are good coders not preferred?
Conclusion
It can be undisputedly concluded that unit testing can be simple sometimes and complex at other times. That is when the tools and frameworks come to your rescue. Even with the unit testing done, the code is not error-proof completely. That is when the next-level testing procedures kick in. Amongst all these uncertainties, the only thing certain is that Unit Testing is necessary.
Recommended Articles
This has been a guide to Unit Testing. Here we discuss the importance, tips, tools, career, and types of Unit Testing with its examples. You can also go through our other suggested articles to learn more –