Testing Overview
There are several levels of testing that can (should?) be included when creating software.
Unit testing
Unit testing is the practice of testing individual components of a software application in isolation to ensure they work as expected. Typically, these components are methods or functions in your codebase. By testing each of these parts you can demonstrate that the component runs as expected, including not only success flows but also when it comes to handling exceptions.
Unit tests should be considered a part of the development process and the time expected to implement and verify the tests should be an integral part of estimates and potentially component design.
When and Where to Use Unit Testing
Early in Development: Implement unit tests as soon as you write new code to catch bugs early. Ideally follow a test driven development approach where you create the tests before implementing the components themselves. This allows you to focus on implementing the components to provide the required functionality with the potential benefit of having thought through the requirements to a lower level.
Core Logic and Algorithms: Focus on critical parts of the application where errors can have significant impacts. Testing simple and "boiler-plate" code can add little or no value, for example simple field setters and getters. Typically an 80% coverage of unit tests on the codebase is considered worthwhile, but this can vary depending on the functionality and requirements of the code itself and the project requirements.
Reusable Components: Test libraries or utilities that are used across multiple parts of the application. Never assume the data entering a component is correct or as expected. Use edge cases and various combinations of values, including null or empty values where applicable, values that exceed expected boundaries (for example try testing a 100 character string where a maximum of less than this is expected and verify the component handles such situations as expected).
Advantages of Unit Testing
Early Bug Detection: Catching bugs early in the development cycle saves time and reduces costs. It can also prove useful for understanding what is missing or still needs to be implemented.
Documentation: Unit tests can serve as additional documentation for the expected behaviour of your code.
Refactoring Safety: They provide confidence to refactor or change code without breaking existing functionality. This is a massive feature of unit testing as a small change in one part of the code may introduce a bug in another component that would not be known, sufficient and pertinent tests can help in exposing such situations.
Improved Design: Writing tests encourages developers to write more modular and decoupled code and promotes a deeper understanding of the component requirements.
Regression Prevention: Ensures that new changes do not introduce bugs into existing functionality, similarly to the refactoring advantages previously mentioned above.
Disadvantages or Negative Consequences
Time-Consuming: Writing and maintaining unit tests can be time-intensive, and changes to existing code can result in many tests needing to be modified. However, in the long term investing time up-front to write tests should actually save you time overall.
False Sense of Security: Relying solely on unit tests may lead to overlooking integration or system-wide issues. Mocking out data, etc. can diverge from the real world and provide false positives.
Overhead: Additional code can increase the complexity of the project and require more resources to maintain.
Extra vigilance is also needed to ensure the tests themselves are not only written correctly and are performing a useful test, but also bug free and aren't making incorrect assumptions.
Inflexibility: Changes in requirements or design can lead to a significant amount of rework in test cases. This should be factored in to any estimates given.