Using Code Coverage as a Check for Test Refactoring
I write about: Software architecture and engineering, Better software better. DDD, Scala and Rust
Search for a command to run...
I write about: Software architecture and engineering, Better software better. DDD, Scala and Rust
No comments yet. Be the first to comment.
In Claude Code, a skill is a modular function-like component that can process requests — like /code-review or /commit. As of version 2.1.1, these skills can be directly invoked through slash commands. So a code-review skill can also be invoked direct...
I have been meaning to share more about my LLM workflows and tooling for a while, partly to have a reference for conversations, but mostly to learn in public. Agent Chisels is where I will be sharing the custom artefacts (primarily skills, with comma...
This year I bit the bullet and moved mail, calendar and drive from Google to Proton. Knowing that my family is not being profiled when, for example, I plan a doctor's appointment feels liberating. Sure, there were fun and useful technical improvement...
This is a short post to share a positive experience I had using an LLM agent to quickly add a feature to an existing personal CLI time-tracking application. Below, I describe how I added it using Zed, Opencode and Claude. To start, I wasn't even sure...
I have been writing "Today I Learned" posts (TILs) for a few weeks now and I started writing them as a complement to long form blogging. But I found myself spending 45-60 minutes, sometimes longer, per TIL instead of 15-25. This defeated the purpose ...
TLDR: Use code coverage reports to verify that test refactorings haven't accidentally changed what functionality you're testing. Same coverage percentage before and after refactoring gives confidence your tests still cover the same code paths.
If we refactor code, tests should cover us from breaking things. But what about refactoring tests? How do we know our tests are still testing the same functionality as before?
While test refactoring isn't widely discussed I found myself refactoring the tests of my applications whilst reviewing and researching testing approaches.
I often use Fluent Test DSLs to develop executable specifications. The DSL may change if it expresses the functionality more clearly or concisely. This might involve renaming a method, changing an assertion, or refactoring the DSL implementation. These changes are usually low risk, but it's helpful to verify we haven't accidentally missed any behaviour.
Sometimes a single test ends up testing too much and you start trimming the test to only validate the exact behaviour you intended to test, possibly aiming for a single clean assert. This sometimes means creating multiple tests to cover the same functionality.
The opposite may also happen. You have too many tests that affect feedback time and you want to combine tests to improve performance. Not ideal but may be a pragmatic approach at times.
Test naming can grow organically and become inconsistent over time. This happens especially when tests use sentence descriptions. Small changes pile up until your tests all follow slightly different naming styles.
You might adopt Testing naming guidelines for consistency. When you start applying them, you realise you haven't reviewed all tests together recently because you've been adding features one by one instead.
During this naming cleanup, you often find other small improvements you want to make. Some tests may no longer seem relevant in the wider context. This leads to more small refactorings.
You might want to use AI to help with these refactorings.
Take the consistent test naming example above. I use AI agents for this task, asking them to review my current tests and align with testing guidelines. I also ask the AI to check for small cleanup opportunities.
An AI assistant can make a plan and list all the changes I need to implement. I then work through them one by one, often by copy-pasting. This isn't the most exciting work, so it would be easier if the AI could make these changes directly after I approve them.
This raises a question: how do I know I'm still testing the same functionality without checking every line of code?
Test renaming shouldn't change the test logic, but splitting or trimming tests might. You might even let an AI agent change code directly, which could lead to unintended changes.
The first check after any refactoring is obvious: do the tests still compile and run?
But how do we know the tests still cover the same functionality after refactoring?
I use code coverage as a basic check for this. Code coverage shows which code lines your tests execute, and it can catch obvious mistakes during test refactoring. If I have exactly the same coverage percentage before and after refactoring (like 94.614%), it suggests I haven't accidentally deleted entire tests or changed which code paths are exercised.
This isn't code coverage's intended purpose, and it has clear limitations - it won't catch changes in test logic or assertions that still execute the same code lines. But it's a useful test for catching the most obvious refactoring mistakes.
I wanted to include the types of test refactorings that code coverage can help verify. These fall into two categories: individual test refactorings are usually relatively easy to manage, but sometimes we need to, or should, refactor tests at the level of entire test suites.
Refactor individual tests when:
The test is too complex or difficult to understand
The test asserts too many behaviours at once
The test is too slow or resource-intensive
The test doesn't match the rest of the test suite style
The test is outdated or no longer relevant
Refactor entire test suites when:
The test suite fails with almost every production code change
The test suite lacks consistency across tests
The test suite doesn't align with current testing guidelines
The test suite has become too large or complex to manage
The test framework or design needs upgrading
Using code coverage as a basic check for test refactorings isn't comprehensive, but it's a simple technique that catches obvious mistakes.
I'm curious how others approach this challenge. Do you have other methods for ensuring your tests haven't been unintentionally modified during refactoring?
Thank you for reading, Hans