
(Image generated using ChatGPT)
In the ideal world of software development, every piece of code would be accompanied by a robust set of unit tests. However, in reality, many software engineers skip writing unit tests, instead relying on minimal manual verification and leaving extensive testing to a dedicated QA team.
Most in the software industry understand the consequences of avoiding to write unit tests, like:
- Increased bugs: Without automated tests, defects are more likely to slip into production.
- Difficult maintenance: Code without tests is harder to refactor and modify safely.
- Higher costs: Fixing issues later in the development cycle is significantly more expensive.
- Slower development over time: As technical debt accumulates, development speed slows due to the need for extensive manual verification.
Many renowned software professionals have highlighted the importance of testing.
- Martin Fowler, said, "Imperfect tests, run frequently, are much better than perfect tests that are never written at all." This underscores the importance of writing tests even if they are not perfect, as they provide significant long-term benefits.
- Michael Feathers, author of Working Effectively with Legacy Code, once said, "Code without tests is bad code. It doesn’t matter how well written it is; it doesn’t matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behavior of our code quickly and verifiably. Without them, we do not know if our code is getting better or worse."
- Robert C. Martin (Uncle Bob), insists that "The only way to go fast is to go well." Writing unit tests may seem like extra effort initially, but in reality, they enable faster development cycles by preventing defects and technical debt accumulation.
Skipping unit tests is like a chef sending out dishes without tasting them first.
In spite of all this, I still see teams not embracing unit testing willingly. Why does this happen? Let's explore the common reasons behind this behavior.
1. Testing is undervalued in formal education
The lack of emphasis on unit testing starts in formal education. Most university curriculums focus on writing code but do not mandate testing as a fundamental practice. Instead, verification and validation are often left to professors or teaching assistants, reinforcing the notion that developers are only responsible for writing code, not ensuring its correctness through automated testing. This gap in education contributes to a workforce that is unfamiliar with or dismissive of unit testing practices.
2. Developers see testing as QA's responsibility
Many developers believe that their primary task is to write code, not test it. This mindset is deeply ingrained in many teams where testing is perceived as the sole responsibility of QA engineers. As a result, developers often focus on delivering functionality, expecting the QA team to handle verification and validation. This division of responsibilities can lead to reduced ownership of code quality and an over-reliance on manual testing.
3. Reliance on QA teams
Many organizations have dedicated QA teams responsible for testing software before release. This creates an environment where developers assume that their code will be tested later, making them less inclined to write unit tests. While QA teams play a critical role, relying entirely on them often leads to defects being caught late in the development cycle, increasing the cost of fixing them.
As Steve Jobs once said, "Be a yardstick of quality. Some people aren’t used to an environment where excellence is expected."
4. Misconceptions about unit testing
Some engineers believe that unit tests do not provide enough value. They may argue that their code is simple enough to not require testing or that integration and end-to-end tests are sufficient. Others assume that since their changes are minor, they do not warrant testing, overlooking the potential for regressions.
5. False sense of security in manual testing
Manual verification often provides a misleading sense of confidence. Engineers may quickly run a few scenarios and believe their code works perfectly. However, without automated tests, regressions can easily go unnoticed, leading to bugs resurfacing in later stages of development.
6. Developers treat unit testing as a separate activity
Developers often call out unit testing as a separate task with its own time estimates and expect clients to approve the additional effort. However, clients are typically focused on features and fail to see the value in developers writing unit tests. Since unit testing is not positioned as an integral part of development but rather as an optional add-on, it often gets deprioritized or cut when time and budget constraints arise.
7. Lack of experience or training
Not all developers are trained in writing effective unit tests. Many engineers, particularly those new to the field, may not fully understand best practices, leading to hesitation in implementing unit tests. Without proper guidance or mentorship, they may view unit testing as optional rather than an integral part of development.
8. Time constraints and pressure to deliver
One of the primary reasons developers avoid writing unit tests is time pressure. In fast-paced environments, engineers are often expected to deliver features rapidly. When deadlines are tight, writing and maintaining unit tests is perceived as an additional burden rather than an essential part of the development process.
9. Complexity of writing tests for legacy code
Working with legacy systems can make writing unit tests challenging. Many older codebases were not designed with testability in mind, making it difficult to introduce unit tests without significant refactoring. Engineers faced with such challenges may find it easier to manually verify their changes rather than investing time in writing tests.
10. Lack of a culture of quality
A significant factor contributing to the avoidance of unit testing is the general lack of emphasis on software quality within many teams and organizations. If a culture of prioritizing thoroughness and long-term maintainability is not established early, engineers may not feel accountable for writing tests.
This issue is further compounded as engineers who do not prioritize unit testing advance in their careers, becoming Team Leads, Tech Leads, and eventually Managers. If they themselves never developed the habit of ensuring quality through testing, they are unlikely to enforce it among their teams, perpetuating the cycle of inadequate testing practices.
Not enough focus is given by leaders on key metrics surrounding unit testing. Tools like Sonar Scan, which can provide insights on code coverage, are often overlooked or not enforced rigorously. Without visibility into test coverage, developers have little incentive to write comprehensive tests.
Additionally, when recognizing team members as sprint heroes or top performers, the focus is usually on how many story points, stories, or features a developer completed. Little thought is given to whether they ensured more than 80% code coverage for each of their stories. This lack of recognition for test coverage further discourages engineers from prioritizing unit testing.
Changing the mindset around unit testing
Engineering Leaders will have to continue to work hard to change this culture in our industry. Some of the things they can do to change this mindset are:
- Promote Test-Driven Development (TDD): Encouraging engineers to write tests before implementing code can lead to better design and coverage.
- Make unit testing a requirement: Teams should enforce unit tests as part of the development process, making them as essential as writing code itself.
- Automate test execution: Integrate unit tests into the CI/CD pipeline to ensure early detection of issues.
- Provide training and mentorship: Invest in training engineers on best practices for writing effective unit tests.
- Highlight long-term benefits: Emphasize how unit tests reduce debugging time and increase confidence in code changes.
- Build a culture of quality: Leaders should emphasize and reward high-quality code, including proper test coverage.
- Expect high quality: Leaders should expect high quality outcomes or excellence from their team.
- Definition of Done: Unit test should be made part of definition of done for user stories.
- Pull request merge criteria: Team members should not be allowed to merge pull requests without a certain coverage percentage on unit testing like 80%.
By shifting the mindset around unit testing and making it a standard practice, teams can build more resilient, maintainable, and high-quality software.