Blog Post

Code Coverage is a Controversial Metric…or is it?

March 27, 2023 Vlad Kobilansky

Woman sitting on laptop contemplating technical debt

Code coverage is a metric used to measure the degree to which the source code of a software system is executed during automated testing. It is controversial because it can be misleading and does not necessarily reflect the quality of the software.
– Anonymous

While the above statement can be true in general, it is worth examining how making coverage data actionable can help us to improve the quality of our production code. Generally speaking, I disagree with the notion that code coverage does not reflect the quality of the software. In some cases it can be true, however, if testing culture is approached correctly and developers do not attempt to cheat by writing purposely awful tests, we can gain quite a bit by improving our code coverage. Let’s take a look…

 

Can Code Coverage Metrics Help You Eliminate Technical Debt?

If one prefers the short of it, then yes, by increasing our code coverage we can indeed reduce the technical debt of the production code.

If you’re new to the term technical debt or tech debt it is a term used in software development to describe the cost of maintaining software that was developed quickly without proper planning and best practices. It accumulates over time and can have a significant impact on a company’s ability to deliver new features and innovate.

Technical debt is often one of the most popular topics in the developer community, and the amount of conversations surrounding the topic of debt elimination has sparked more than one debate. How much debt is acceptable? How do you measure it? What does it mean for my team? If you follow SOLID principles and other best practices, could you possibly have technical debt?

These questions already reveal a problem: there is no defined way to measure technical debt. It’s tough to get rid of something if you can’t figure out how much of “it” you have. Although numerous tools exist that help you to analyze your code and seek how to offer a measurement of technical debt, there is no universal standard, which can let a developer be 100% certain that their code has absolutely no tech debt.

Maybe a little debt is ok?

Tech debt is a concept whose definition correlates with our real-life debt, such as credit card debt, quite well. Unfortunately, the analogy falls short once you dig into the actual details of tech debt in a code base. Even though the “bankruptcy” might be real and you can paint yourself into a corner with spaghetti code that’s completely untestable and has to be refactored to add new features, there is no such concept as an interest rate. However, as you write more ugly code on top of already poorly written code your technical debt will increase exponentially, but again, that is not something you can easily quantify.

To follow along with this analogy, a little debt is indeed OK as long as you have a clear plan for how to get rid of it. (Repayment).

Now, what about testing?

We’ve probably heard of terms such as “test-driven development” or “behavior-driven development”. These are great practices that allow us to write code that can be tested automatically and that is a key concept to remember or to consider: Testable Code.
This is where the elimination of technical debt and coverage data intersect. As we know, by eliminating technical debt we are increasing our code quality.

Writing testable code can help you avoid tech debt in several ways:

  • Testable code makes it easy to catch bugs early in the development process: By testing your code, you can identify problems before they become bigger issues. This saves time and resources by avoiding the need for extensive debugging later on.
  • Testable code is easier to maintain: When you write code that is easy to test, it is also easier to modify and update. This makes it more straightforward to fix bugs and add new features without introducing new errors.
  • Testable code is more scalable: As your application grows, it becomes more challenging to maintain and test all the code. By writing testable code from the start, you can ensure that your application is scalable and can handle a larger codebase.
  • Writing testable code helps improve the quality of the code: By focusing on writing code that is easy to test, you also write code that is easier to understand, less prone to errors, and more reliable.

Writing testable code can help you eliminate technical debt by ensuring that your code is reliable, maintainable, scalable, and of higher quality. By taking the time to write testable code from the start, you can avoid the accumulation of technical debt, and ultimately deliver better software.

Enter code coverage.

With this in mind here’s where code coverage shines as a metric. Here’s how you can gain even more value by leveraging a code coverage tool like Codecov:

  • Identifying untested code: Code coverage can help identify areas of code that have not been tested yet. This can help developers prioritize their testing efforts and ensure that all code paths are covered by automated tests.
  • Tracking progress: Code coverage (and Codecov specifically) can be used as a progress metric, allowing developers to track how much of the code has been tested over time. This can help identify areas where testing efforts need to be increased and provide insight into how the software quality is improving over time.
  • Improving maintainability: Code coverage can also improve the maintainability of the software. By having automated tests in place for all code paths, it can be easier to modify or refactor the code without introducing new bugs.

And that brings us to the overall benefits of Testable Code. If our code is structured in a way that makes it easy to test and cover with a multitude of test cases, we can rest assured that our code quality has improved.

 

Testable Code is the Quality of Software We’re Trying to Achieve

So at the end of the day can we say that by increasing our code coverage we are actually decreasing technical debt? Well, I’m leaning toward “yes” – the more testable code we have, the better the quality of our production code.

Testable code is code that can be easily and effectively tested using automated tests. Exactly as we execute them in our CI pipelines. Testable code is designed in a way that makes it easier to write, maintain, and run automated tests. It typically has the following characteristics:

  • Modularity: Testable code is designed in a modular way, with separate functions, modules, or components that can be tested independently of one another.
  • Low coupling and high cohesion: Testable code has low coupling between its components, meaning that changes to one component do not affect the behavior of others. It also has high cohesion, meaning that each component is focused on a specific task or function.
  • Clear and consistent interfaces: Testable code has clear and consistent interfaces between its components, making it easier to understand and test how the components interact with each other.
  • Minimal side effects: Testable code has minimal side effects, meaning that it does not have unintended consequences or modify the global state in unexpected ways.
  • Separation of concerns: Testable code separates concerns between its different components, meaning that each component has a specific and well-defined responsibility.
  • Dependency injection: Testable code uses dependency injection, meaning that components are designed to rely on abstractions rather than concrete implementations. This makes it easier to test individual components in isolation.

By following these principles, developers can create code that is easier to test and maintain, leading to more reliable and higher-quality software.

We shouldn’t view code coverage as a controversial metric, because it may not immediately reveal issues with the test or code quality. Rather, code coverage helps you identify which parts of your code are not being tested. These untested parts of the code are where bugs are likely to hide. We can shine an imaginary flashlight by writing a test for a legacy (buggy) part of our application. The more lines we uncover, the less place there are for bugs to hide. By identifying these areas, you can ensure that you are writing tests that cover poorly written or legacy parts of the code. By doing so, you reduce the likelihood of bugs slipping through your testing process.

Code coverage is important for the elimination of technical debt because it helps you ensure that your testing strategy is comprehensive and that all critical parts of your code are being tested. By identifying untested areas of your code and redundant tests, you can refine your testing process and catch bugs early. This ultimately leads to code that is easier to maintain, less prone to errors, and of higher quality.

Before we redirect you to GitHub...
In order to use Codecov an admin must approve your org.