Using "eat what you cook" API testing
Stijn Dejongh
Hands-on software solution architect | freelancer | husband | father | human
I was inspired by a section of the book "97 things every developer should know" ~ Kevlin Henney et al.
Currently, I am experimenting with a test-supported design technique, which is basically "eat what you cook".
Eat what you cook
The basic idea is that when you are a developer working on a library (or other external API) meant to be used by others, it is beneficial to add a simple application that uses your own code to your test suite. This way you get a feel for what it is like to use your own constructs.
experimentation took place on my breakable toy project, which is meant to generalize object validation logic, and gives developers a fluent but powerful API. (source code on github)
Some context on the goal of my library
Over the years, I have worked on a variety of projects. While the business domains varied, the core of these applications usually revolved around moving data records around. In doing so, the concern of data validity pops up quite often.
Validation of a record can either be self-contained within the data it stores, but more often than not depends on relationships and business rules that are not easily enforceable with regex based checks. These validations often require interaction with external systems, or applying computational rules.
In my experience, checking the validity of a data record is done in vastly different ways on different projects. The resulting code however often ends up being tedious to write, even worse to read, and often based on control flow interruption mechanisms (most commonly: exceptions).
Troubled by this, I resorted to creating monadic wrapper objects to contain both my data under inspection, and any validation errors that occurred on it. As an operation performed on a monadic object is expected to project the monad onto itself, this allows for a more fluent way to write these assertions, in a chaining manner.
The Fallible class is a construct that wraps together:
It's internal state look like this:
Trying to write a simple validation rule
The object to validate is a simple construct that represents a contact for an emailing service.
领英推荐
Now, we wish to validate that this object contains a valid email address. For simplicity's sake, this address field is considered to be valid if it is filled out, and does not contain the word "invalid".
Here is where I realized I had messed up the public API of this class. I wanted to create a generic utility that was able to deal with various complex use cases. But in doing so, implementing a simple validation rule showed I had severely over-complicated things.
See for yourself:
magine trying to write that code without in-depth knowledge of the library...
After I wrote this code in my "LibraryUsageTest" and asserted it works, I imagined what I would rather the code looked like. Using a powerful technique called "wishful thinking", I wrote a few lines of comments to show how I would like this to look. The key here is that I told myself not to be bothered by how I would go about doing that, or whether it was even within my ability to do so.
And then ... I basically just got on with it. Trying ways to refactor my first API into a more readable version, and making sure to not break either of the two assertion clauses shown below. This way I was sure the redesigned API methods did the same as it's more convoluted older sibling.
So ... did it work?
I managed to get pretty close to hitting the mark, but have not found a way to get rid of the need for explicitly typing my static factory methods.
Here is what I ended up with:
TL;DR
I wrote a simple test class that used my own library. This made me realize I had not done a very good job of hiding internal complexity. So I tried to fix it.
Consider using a similar approach if you are writing library code, frameworks, or any general purpose code utility for your colleagues to use.