Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. Even though manual unit testing is not unheard of, it is often preferred to have automated unit tests. In simpler words, unit tests help you find bugs in software and make sure the same bugs don’t cause the application to break again.
Testing often seems like a pain to developers and a time-consuming job with no actual output except a validation of the development code itself but it is important to realize that testing is as essential for a developer as writing production code.
For one, tests will help you avoid delivering sub-standard code. A comprehensive test suite is extremely useful when adding a new feature to ensure that any modifications that you made don't break the existing code base. Further, when refactoring or removing redundant code, tests would be there to ensure that no corner cases are being violated by your changes. Keeping code clean is incredibly important, especially for vast open source projects.
Secondly, tests help increase code stability. If each commit is tested before deploying, it would ensure that the release version is always a stable build and no users are being impacted by buggy developmental code.
Not just this, tests also supplement your development code by helping new contributors get familiar with the possible tests cases they should keep in mind while making any changes.
Best practices while using TDD
Prepare a roadmap: Test driven development rests on a ‘Test first, Implement later’ approach. Before working out the actual code. It is important to flesh out the cases where the code should give a false or incorrect outcome - this forms the red part of the red-green routine of TDD. The valid inputs and their outputs from the green part.
Reduce functional complexity: Sometimes, it may be very easy to get caught up in the finer details of implementation and write very complex test functions. It is important that your tests cover all possible functionality with the least complexity. After each refactoring of the test classes, they should be reviewed for correctness and completeness.
Test repeatedly: The test development life cycle is hinged on repeated testing and refactoring of the code. Any code that is added to the existing codebase should ideally not break any existing use cases and pass all the tests. But to ensure the code sanctity is maintained, it is essential that the tests be updated after each cycle of code refactoring and development.
Naming and implementation Conventions: Naming conventions help organize tests better so that it is easier for developers to find what they’re looking for. One commonly used practice is to name tests the same as implementation classes with suffix Test. If for example, implementation class is exampleClass, test class should be exampleClassTest. It is also useful to organize your tests in a similar structure to your implementation code except in a different folder.
Know when to use TDD: Much like any other coding practice, TDD is also effective in only certain cases. For the most part, using TDD is recommended wherever testing can be done quickly and efficiently. If TDD slows down or prolongs the development lifecycle, the entire purpose of the exercise is defeated.
When should tests be written?
Test-driven development (TDD) guidelines mandate that developers must write test functions before the actual code but most developers prefer writing testing suites retroactively. However, for most developers, it doesn’t actually matter when you write the tests as long as you actually write the tests.
How do you check the quality of your tests?
A common metric for testing the quality of your testing suite is called code coverage. Code coverage is a number which conveys the percentage of your development code that is covered by your test function. It helps you catch the corner cases you might have missed testing.
Several organizations use code coverage as a part of their development pipeline and no commit is allowed to be deployed unless it achieves the required code coverage (a minimum of 90% across most projects).
Is code coverage by itself sufficient?
While code coverage is important and absolutely essential, it is not an adequate metric for checking the quality of test suite. It simply checks for the number of lines of development code that are being tested.
For example: If you are testing a function for finding the maximum in an array, you may be testing the functionality of the function and get 100% code coverage but not test cases such as when the array is out of bounds. Hence, manual testing is also important.
The best practice is to write tests as you go. The developer who writes the development code is the best person for writing the testing suite.
Ensure maximum code coverage. Keep in mind the 80–20 rule of code coverage (20% of time will be spent in achieving 80% code coverage and 80% of time will be spent in achieving the remaining 20%)
Manual testing of code is important after automated testing is complete. There is no substitute for peer review and feedback.