How To Write Unit Tests, Elegantly
There are two different approaches when writing unit test-cases is concerned: You follow a Test Driven Development and then, later on, develop your code from there. The TDD approach originated during the early stage of Agile development, most probably in the early 2000s. This is where our second point comes into the picture. You write test-case to make your product mature. You will be aware of any faulty code that has entered your codebase. Be negative with your test class. Don’t go looking for a test cause a problem, that’s correct.
Software developer at JP Morgan chase
“If you don’t like unit testing your product, most likely your customers won’t like to test it either.” — Anonymous
Most of us developers have become a bit lethargic when it comes to writing unit test-cases. Why do we get that resistance?
I graduated from NIT-TRICHY in 2018. I’ve started my career in JP Morgan by writing Junit test-cases for a legacy application. I can tell you verily, that it's exhausting to write test-cases for someone else’s developed code. I was frustrated. Like every other aspiring CS graduate, I too had a lot of expectations from my corporate life, like, develop some mind-blowing product, work on the cloud, play around with BigData, and many more. However, corporate life is not always a bed of roses :D.
There are two different approaches when writing test-cases is concerned:
- You follow a Test Driven Development, where you first write the test-cases and then, later on, develop your code from there.
- You write test-cases when there is a need for regulatory compliance for your legacy product.
We all know that the TDD approach originated during the early stage of Agile development, most probably in the early 2000s. Recently, most of the product-based companies adopted Agile standards, and test coverage is one of the crucial attributes of Agile development. So what about the products before Agile? This is where our second point comes into the picture. You write test-cases to make your product mature.
What do we gain by writing test-cases?
Now we know that writing test-cases is a necessity for your application as it is one of the most important attributes which qualifies your application as mature. On the other hand, you may think, my application is working alright! Why do I need to write test-cases to verify the same?
Let me just run you through a few of the problems that you may face if you don’t have proper test-cases in place ☺.
- Pair programming is a common term in Agile standards where you work with your colleagues in the same code base using agreed web-based version control and collaboration platform, like GITHUB. In this methodology, there is a lot more chance that the feature you were working on was hindered by your partners’ feature changes. These two features may be completely different from one another, but it might cause your feature to fail unreasonably. Later on, to find the bug and fixing it, is such an overhead. If you have proper test-cases in place for your feature, the proximity of finding the bug is easy, reducing your precious time.
- You might lose control of your application. Day by day, your product will receive a lot of requirements and changes. Your application will grow enormously. Even a single bug in your code can cause multiple failures and a hell lot of confusion. By writing test-cases, you will know every change that is happening in your system. You will be aware of any faulty code that has entered your codebase.
In Agile development strategy, these two problems are the most common ones that are faced by any product.
How to write elegant test-cases to avoid such detrimental failures?
When you start writing your test-cases maybe you could follow a certain methodology which may ease your way of writing test-cases. I’ve encountered a few of those when I started writing test-cases, let me chart it down in points.
- When you start developing a piece of code, always start with your test class. Make sure that you have a very basic runnable code in your main class and check for a call from your test class. If you can do it then you are done with your first step.
- Always be negative. That’s correct, you heard me right. Be negative with your test-cases. Don’t go looking for a successful test run, it may cause a problem in the later stage. Write test-cases to make your system/feature to fail. Subsequently, start to refactor your code to counter this failure. In this way, you can ensure that your under-developing code is unbreakable. For instance, let’s say I have a feature to develop which will get me a set of registered users between two given timestamps. I would start by providing invalid inputs (Say, invalid date-time format) to my system and check where it fails (If refactor is required, I will have to work on it). Some of these failures might cascade to your system failure. Now you have encountered a serious issue without a Production push. In this way, you ensure your clients that you have a high availability application that doesn’t break for any mistake that is provided at the client’s end.
- Try figuring out multiple scenarios for a single code flow. Try to write test-cases for all those scenarios. It makes your system robust. Try covering exceptions and all valid/invalid flows.
- It’s always said that a good, mature application should have a coverage > 90%. Coverage as a whole depends on a few factors; # of lines and # of conditions or branches. Total coverage = (# of lines covered + (# of branches covered)*2)/ Total # of lines. The number “2” here is because of each IF and ELSE block. By covering most branches you should be able to cover almost 90% of your total code. These two factors are the basic blocks for your test coverage.
- Usage of mocks is always important when you write test-cases. Mock a service when it’s required to return a set of test data that you need to test your significant code.I have seen people using Reflections in Java to set or test private fields. Avoid reflections to test or set your private members. By using reflections you are defying the sole purpose of a private body. Instead, load a test context during runtime which will supply data to your private fields.
I guess I have covered the most basic details that we should be aware of when test-cases are written. As mentioned in the earlier quote, a product without unit testing is untrustworthy and vulnerable to all sorts of complications.
A robust code is the one that is unbreakable and has high availability during all circumstances. Speaking of high availability, for my next article I’ll write up about the necessary resiliency strategies for architecture to ensure high availability and fault tolerance in your system.
Let’s start writing test-cases with a mindset that it’s not just a part of quality testing, but it’s an important part of robust development.
Thanks everyone for reading till the end. I hope it is a useful article.
All praise is to the Lord.
P.S. — This is my first article. Kindly, please do take time and provide some constructive feedback for my growth. Thanks ☺
L O A D I N G
. . . comments & more!