In this article we will discuss about unit testing, why we need it and what are various quirks in implementing them.
In today’s world of ever increasing complexity of software, where requirements are always changing, we need to control cost of delivery with highest quality. Whoever worked on medium to long term projects will know the complexity of bug fixing after release and maintenance.
Cost of fixing bug increases with lifecycle of software. During requirements phase it costs cheap, and it increases with stages like development, testing, maintenance. To detect bugs in early cycle of development we need a mechanism. Unit test provides a mechanism to detect bugs in development cycle.
What is Unit Test?
In unit test we will take unit of code and isolate it from dependencies and inspect it for any defects. Mostly unit will be a method or set of method in case we are testing public APIs.
How to write Unit Tests?
Before discussing how to write unit tests, let’s inspect problem more thoroughly so that we can understand our approach better.
In development mostly we find following problems relating to quality
1. Requirements not implemented.
2. Requirements not implemented properly.
3. Missed Requirements (Requirements are not defined)
Developer might have missed some requirements to implement in production code, few times implemented but not correctly implemented, in other cases requirements are not defined, but developer has implemented them during development.
Our unit tests should be able to detect above problems. There are three unit test approaches to tackle above problems
Structural Unit Testing
In structural approach we will try to write unit test cases based on production code we are trying to test. Here we will use code coverage, number of operators, operands in a statement, number of parameters as benchmark in deciding how many tests cases are required.
a. We will try to achieve 100% code coverage.
b. Depending upon number of parameters, number of ways to invoke method will increases. To reduce complexity try to keep number of parameters to minimal. We will try to write unit test cases covering all parameters.
c. Need to write test cases using Boundary values.
Functional Unit Testing
Using functional testing, we will write unit test cases for each requirement. Here we will take requirement/functionality as unit try to test in that aspect isolating class from other dependencies.
No approach – doesn’t have specific set, but written by experienced developers who will be able to think of cases using which they can test class. It can effective but not a systematic way to assure things are always the way we wanted.
Using Structural testing we will be able to test missed requirements, bugs in code. Using functional testing we will able to detect improper implementation of requirements and bugs as well.
Does it solve the problem?
Unit tests are better to ensure quality to a finite set of scenarios you had tested. You can reasonably assure that for scenarios you had tested, your code will work. Unit tests acts as a safety net whenever code changes are required. There are still uncertain scenarios which are not tested, where bugs might be lurking. Overtime you will reduce uncertainty in quality.
I can’t afford to write Unit Tests?
In this fast paced world, it looks obvious that there is no time to write unit test cases. By not writing unit tests, you are increasing uncertainty in your code quality. If you take a horizon of more than one year for your code base, by investing in Unit Test you will be reducing effort required for testing and maintenance. Cost is less during development, but increases in maintenance. Unless are running away by not maintaining your code base, you will reduce costs by investing in unit tests in maintenance phase which will be huge.
What about Integration Tests?
You should be able to test Integration tests as well in Unit Test. Integration scenarios are scenarios where class being tested will rely on another class to perform certain task. In those scenarios, typically you will be using mocking dependencies and setting expectations. It is expected by dependent object to perform those expectations as is envisioned in our unit tests. Make these expectations as unit tests of dependent object. By ensuring this you are testing integration scenarios as well. It requires careful planning but never impossible.
Apart from that make your classes less chatty. Reduce the surface area of interaction between classes. By doing this you are decreasing integration scenarios and making fewer expectations.
My class is Un-Testable?
During testing most often we will stumble upon code which is not testable. Most of the times you should be able to refactor code and make it testable. If you are using Static Method invocations, which are not testable, you can create a proxy and use this proxy to interact with static objects and methods. By doing this you can mock proxy and make this untestable code into testable.
Reduce Maintenance of Unit Tests
As with any code, overtime as with changing code, corresponding unit tests will also get changed. To reduce maintenance cost of unit tests without compromising quality, you can test only public APIs, i.e. Public methods, as these are the methods used by users. Using these methods you should be able to invoke private and protected functionality as well and should be able to test them. This is sort of adding features of structural testing into functional testing and should be able to achieve same results. By testing only public API now your unit tests are more resilient to refactoring of class internals.
If you take cost of software, maintenance costs are huge than construction costs itself. In order to be properly equipped to reduce maintenance costs, unit tests are most indispensible tool. Unit tests reduce uncertainty in your code.