Overfullstack Overfullstack
Test Driven Design

Score the Tendulkar Test Coverage

Right design can boost test coverage organically

Nervous Nineties

India’s most renowned cricketer Sachin Tendulkar holds a peculiar record that NO batsman wants to break. He missed many centuries by getting dismissed at the score of 90 (18 times in ODIs and 10 times in Test cricket). He holds the record for the highest number of dismissals in the 90s (a total of 28 times) across all forms of international cricket. As a Tendulkar fan, I knew almost each of these scores by heart, as I felt equally nervous. When I see code coverage numbers hitting more than 90, it reminds me of Sachin’s Nervous Nineties

Abstract

Audience

This applies to software developers at all levels interested in good Software Architecture. I use Java code snippets for demonstration. PowerMock may be specific to the JVM ecosystem, but there may be analogous tools in other ecosystems. Ports & Adapters is a language-agnostic architectural pattern.

Takeaways

Resources

Why Unit test?

What makes Unit testing difficult?

There are so many test anti-patterns, you may refer to the resources cited for an in-depth explanation. We are going to focus on a few primary factors, that are prevalent across almost all code bases, and simply correcting them gives us maximum returns.

Code Smells 🐽

Bad Design 💩

Your component is interacting with your external systems directly, without explicitly declaring them as dependencies.

Legacy Code Interactions 🗿

If you interact with Legacy code, your code instantly turns legacy

To guard a component behavior with Unit-tests, one should Strive to achieve behavior coverage, and not statement coverage. Unfortunately, the code-coverage metric currently available to measure this Behavior coverage is only through the no.of statements or LOC covered. That’s in a way a Loophole! Like anything in our society, a loophole combined with an obsession to achieve high coverage numbers leads to some shady tools and practices that cut corners, which defeat the purpose of why coverage exists in the first place.

I have the “Power” Mock

Are you saying one can’t achieve high test coverage with these obstacles? The obstacle is the way and I have the “Power”Mock. These are some of the dark powers it gives you:

What else do you need!? you should feel unstoppable!! When you have a hammer, everything feels like a nail. Check-out how you can achieve 100% test coverage even without testing any behavior: Example

Synthetic Coverage

Altogether a new Framework to learn

Brittle and Hard to maintain tests

Compatibility

The last nail in the coffin is PowerMock’s poor compatibility with other test tools:

Why is PowerMock Invasive?

But do I need Unit tests at all?

With all these obstacles and PowerMock dark powers, do unit tests even add any value to my automation? I better rely on my Integration Tests (ITests)

Integration Tests (ITests) vs Unit Tests (UTests)

I must emphasize, please don’t use ITests to replace your Utests. That’s like taking a full bath each time you pee, instead of just hand-washing.

Purpose

ITest is supposed to emulate how the customer uses your application in the real world, with real DB and network calls. So, you need to limit them to test E2E use cases and not for every perm & comb.

Traceability

ITests are bad at giving pinpoint feedback. Unit tests do an excellent job of that. We have so many ITests with just a param difference. Mostly, they might be testing a validation for which they don’t need the server to be running.

Productivity

ITests, both writing, and running are costly and one of the major productivity killers.

What if I told you instead of trying to overcome those obstacles using invasive tools like PowerMock, you can get rid of those altogether and write meaningful Unit tests and achieve organic Unit test coverage that matters?

The Redemption

Code Smells

void methods

static methods

Pure vs Impure

Impure

Functions that perform side-effects like DB interactions, Network calls, Emitting events, logging, etc.

impureFn
static void add(int a, int b) {
System.out.println(a + b); // Side-effect
}

This is not easy to test. It’s a void method performing an impure operation of printing to the console

Pure

pureFn
static int add(int a, int b) {
return a + b;
}

This is a no-brainer to test. But it’s not always possible, sometimes they are too intermingled. In that case, pass the side-effect as a parameter for the function. This makes it easy to fake during testing.

higherOrderFn
static void add(int a, int b,
Consumer<Integer> consumer) {
consumer.accept(a + b);
}
// Function call in Prod
add(1, 2, System.out::println);
// Function call in Test
add(1, 2, result -> assertEquals(3, result));

Bad Design

Legacy Code Dependencies

Ports & Adapters 🔌

Black-box Unit testing 🕋

Mock Dependencies NOT statements

Demo

sttc-demo

Before:

PokemonCollector

After:

PokemonCollector

You can execute a test without worrying about what statements to mock. Test as if you don’t know how it’s implemented. Instead, fake the dependencies and assert how the component behavior changes with it.

Conclusion

It’s easy to design right than to depend on invasive tools like PowerMock. Don’t let the existing legacy code be an excuse for bad design.

No one gives you change, you need to bring change

A Bus conductor told the above quote (now read the quote again) — #dadjoke

My Talks on this

Resources

Back to all articles