AHA Testing is an approach based on the programming principle “Avoid Hasty Abstraction,” offering essential guidelines for writing maintainable test code. By controlling the level of abstraction during the test code writing process, developers can create tests that are easier to understand and maintain.
Balancing Abstraction is Key
When writing test code, setting the abstraction level too high or too low can make maintenance challenging. AHA Testing aims to find this balance. For instance, overapplying the DRY (Don’t Repeat Yourself) principle to reuse all code can make it harder to understand and lead to significant confusion when changes are needed later. On the other hand, without any abstraction, the code can become overly repetitive and difficult to maintain.
ANA (Absolutely No Abstraction) Testing
The Problems of Non-Abstracted Tests
ANA Testing, which involves no abstraction, can lead to extremely difficult-to-maintain code. For example, if you write out the entire logic in each test when testing an ExpressJS route handler, the code becomes lengthy and complex. This structure hampers consistent maintenance and increases overall codebase complexity.
Let’s look at an example of such test code:
import * as blogPostController from "../blog-post";
jest.mock("../../lib/db");
test("Retrieves a list of blog posts for a logged-in user", async () => {
const req = { ... };
const res = { ... };
const next = jest.fn();
await blogPostController.loadBlogPosts(req, res, next);
expect(res.json).toHaveBeenCalledTimes(1);
expect(res.json).toHaveBeenCalledWith({
posts: expect.arrayContaining([expect.objectContaining({ title: "Test Post 1" })]),
});
});
This test code is easy to understand at first glance, but having multiple similar codes can make maintenance very challenging later on.
DRY Testing
When Attempts to Avoid Repetition Create Problems
Conversely, DRY Testing can cause problems by pursuing abstraction excessively to avoid code repetition. Trying to reuse all code can make it difficult to follow the flow and leave complex logic in a state that is hard to comprehend.
For instance, when extracting setup code common to several tests into a separate function or utility for reuse, over-abstraction in this process can reduce code readability.
function setup(overrides = {}) {
const req = { ... };
const res = { ... };
const next = jest.fn();
return { req, res, next };
}
test("Retrieves a list of blog posts for a logged-in user", async () => {
const { req, res, next } = setup();
await blogPostController.loadBlogPosts(req, res, next);
expect(res.json).toHaveBeenCalledTimes(1);
});
In this example, the `setup` function reuses common code, but if it becomes overly complex, maintaining the code could become difficult.
The Advantages of AHA Testing
AHA Testing is an approach that aims to enhance code comprehension and maintainability by avoiding hasty abstraction and achieving an appropriate level of abstraction. This makes the code easier to read and maintains a state that is easy to handle for future changes.
For example, writing a function to avoid repeating certain setups in test code and reusing it across multiple tests is a good approach. However, it’s essential not to let this function become so complex that it makes understanding the test logic difficult.
Conclusion
AHA Testing is an effective method for enhancing code maintainability. The key is to balance abstraction well, creating code that isn’t overly complex yet allows for the necessary parts to be reused. This enables developers to better understand the code and adapt flexibly to future changes.
Reference: Kent C. Dodds, “AHA Testing”