Abstraction is one of the founding principles of software. Over the next couple of articles, I’m going to talk about it, and how it relates to testing. Without proper abstraction, we’re risking going barefoot into fragile-test land. In fact, it’s one of the main reasons we blame tests for breaking, when it’s not really their fault.
For my Clean Code peeps – You know the value of abstraction already. I think people understand the concept, but not its power (for good and evil), and how to use it with modern tooling. Since tests are first-class users of the code, they are the first point where we can observe that power.
Abstraction? What is it?
Let’s start with the almighty Wikipedia definition:
“Abstraction is the process of generalizing concrete details, away from the study of objects and systems to focus attention on details of greater importance”Wikipedia
As you can see, this is pretty abstract. Must have been written by a consultant.
Dig in, and you’ll come up with the “forest from the trees” idea. Seeing the big idea, rather than the small parts.
We do this everyday. Whenever we write a piece of code, we use (mostly) English words to tell the computer what to do. But what are we really doing?
The computer deals with zeros and ones. It’s a completely different language. The real clients of the English code is us. We use a programming language to abstract the binary code. To move between languages, we need a translator – a compiler. And it better be a good one, otherwise, the abstraction’s not gonna be good enough.
Because the more inaccurate that translation, we get more bugs. The user of the APIs expects something to happen, and then, well, gets surprised.
Which is where we get to testing. Black-box testing tests the abstraction. If we get the result that we want from our black box, it really doesn’t matter how it’s built on the inside. If it doesn’t, we’d know if something is wrong.
Black-Box Testing Is Abstraction-Based Faith
The assumption behind black-box testing is the “user” only needs to understand the interface to operate the box. The abstraction is complete.
The appealing thing about black-box testing is exactly that – if a test passes through the agreed interface, and checks the correct behavior, we believe that regardless of what’s happening inside, the test will tell us if we broke something, or everything is ok.
This belief is based on complete abstraction. However, Complete abstraction means completely correct translation.
What if the translation is wrong? What if it’s partially correct? What if it has dependencies that are not fully controlled all the time?
In the more benign case, the test will fail for the wrong reason. Still ok, maybe a bit waste of time. On the worse side, the test can also pass for the wrong reason. And we don’t want that.
Tests give us confidence that the code works the way we expect it. But tests passing without spotting the defects, ruin our confidence in our tests.
And then there are the fragile tests – those that are not consistent. Those that fail because we didn’t imagine failing on a thing we didn’t think about.
The ones that broke the abstraction contract.
Here’s the kicker: We can believe in the abstraction. But we’ll have more confidence in the tests, more if we understand and trust the translation. If we know the code, architecture, the environment, the context.
Abstractions are not binary. We can tell if things are abstracted better, based on the knowledge of the inside, what the code does, what it handles and how.
Proper black-box testing is reliant on understanding the abstraction. How complete, accurate, and consistent the translation is. In other words, seeing through it.
And in the next articles we’ll look at examples of those. First stop: Mocks.
Meanwhile, check out the “API testing for Developers” where abstraction is all the rage. Real rage.