How Behavior-Driven Development Saved My Code from the Jedi Interruption

So, I’m not the sharpest sandwich in the drawer. This was demonstrated to me again in no uncertain terms just the other day, as I was working up some routing logic for a new Durandal-based app. The JavaScript in question went something like this.

router.guardRoute = function (instance, instruction) {
    var routeInfo = instruction.config;
    if (!_isAuthenticated && !routeInfo.settings.anonymous)
        return getAuthenticationUrl();
 
    return true;
};

It’s not an original story. guardRoute runs every time the user tries to navigate to a view in the app. This snippet simply ensures that unauthenticated users get redirected to a login page if the new route is not available to anonymous visitors. And it hummed along happily for a short while.

When Suddenly

Apparently, if the optional settings property is not present on routeInfo, an exception gets thrown, all of the air leaks out of our app, and the rotation of the Earth sort of grinds to a halt. Could be a bug.

No worries, thought I. We just needed to test route.settings before testing route.settings.anonymous.

if (!isAuthenticated && !route.settings && !route.settings.anonymous)

Hmm. That wasn’t quite right–what if we grouped those second two conditions together?

if (!isAuthenticated && !(route.settings && route.settings.anonymous))

That felt correct. But static analysis alone wasn’t enough to give me a warm fuzzy that my code was up to its arguably very important job.

Like I said, I’m not the brightest knife at the gun fight. I was standing on my head and counting on my fingers to ensure that this code was going to work. Maybe if this was the only thing I had going on at the time, perhaps if I was able to hone a laser-like beam of attention on the problem, I could have gotten to the point where I was confident in its correctness.

But, as it happened, at that moment my tête-à-tête with reinventing the authentication routing wheel took a back seat to making a peanut butter sandwich for this guy.

jacob

To clarify, I was working from home that day. I mean, I wouldn’t want you thinking that Aptera keeps the kind of office where tiny hungry children just sort of wander around in Jedi cosplay and dislodge people from their respective trains of thought.

Anyway. By this point I was spreading peanut butter on bread and spiraling hard into a full-on Hanselman phony complex on account of this gear just not catching in my head. What was wrong with me?

Enter BDD

As is often the case, the solution was there before the problem. I just needed to take a few minutes to wire up some Jasmine specs.

Who?

Let me back up. By now, you probably have heard all about the wonders of Test Driven Development (TDD). How it will make your code flawless, fix your marriage, and scratch that itch in the middle of your back that you can never quite get to. But you may not be familiar with TDD’s more stylish younger brother, BDD. That’d be Behavior Driven Development, if you’re not into the whole brevity thing.

The thing about traditional TDD is that it has a pretty fundamental flaw–it is backward. Backward, at least, in terms of its underlying metaphor of testing a thing. See, in meatspace, in order to run a test on something (like in a laboratory), it first needs to exist. So when we’re told to write a test for code that doesn’t yet exist, we buck; how can we write a test when we don’t even know what our code looks like yet? If TDD is something you’ve been meaning to get into, but you could never quite find the front door on it, this is probably why.

The behavior-driven approach resolves this cognitive dissonance, not by being mechanically any different at all from TDD, but by flipping the assumption that what we are writing first is a test. What we write first, says BDD, is a spec–a specification that defines exactly what the code under test will do. With this, our brains crack into applause–we write code from specifications all the time.

In execution, though, a spec is just a unit test that’s named well. Here’s how a typical test suite (using QUnit here) might come off in the naming department.

module("PB and J tests");
 
test("bread test", function(){     
    // assert stuff about the bread
});
 
test("peanut butter test", function(){     
    // assert stuff about the peanut butter
});
 
test("jelly test", function(){     
    // assert stuff about the jelly
});
 
// etc.

Make a barf noise here, amirite? Here’s how we’d name things using the aforementioned Jasmine, a BDD framework for testing JavaScript code.

describe("A peanut butter sandwich", function() {
    
    it("has two slices of bread", function () {          
        // assert stuff about the bread     
    });         
    
    it("has peanut butter on one slice", function () {        
        // assert stuff about the peanut butter    
    });
        
    it("has jelly on one slice", function () {          
        // assert stuff about the jelly    
    });

    // etc.
});

What we expect to have happen in our code is just so much clearer when we spell it out in English1. My very favorite thing about wording each spec like this is that Jasmine fuses the title from describe() with the phrase from each it() as subject and predicate. The output shown in the test runner then becomes a series of transitive statements about the code under test.

  • A peanut butter sandwich has two slices of bread.
  • A peanut butter sandwich has peanut butter on one slice.
  • A peanut butter sandwich has jelly on one slice.

And, of course, each of these comes with a red or green mark that shows us whether it is true yet.

The value of a good suite of BDD specs is that it is a direct English translation between our expectations for the code that we haven’t written yet, and the code that will test it to ensure that it meets the specification. The spec says what our code does. Our code does what the spec says. When we’ve made it all turn green, it works.

And here’s the part where I actually solved my problem

So cut back to the other day, post-peanut-butter-and-jelly-hiatus. I stopped banging my head directly against my troublesome code, opting instead to simply define, in plain English, what I wanted it to do.

describe("The auth service", function () {
});

A rousing start. Yessir, the auth service is in fact what I would like to be describing today.

describe("The auth service", function () {
 
    it("indicates when the user is authenticated", function () {
        var a = new auth(mocks.api);
        spyOn(mocks.api.login, 'GET').andReturn('jfazzaro@apterainc.com');
        a.start();
        expect(a.isAuthenticated()).toBe(true);
    });
 
    it("indicates when the user is not authenticated", function () {
        var a = new auth(mocks.api);
        spyOn(mocks.api.login, 'GET').andReturn(undefined);
        a.start();
        expect(a.isAuthenticated()).toBe(false);
    });
 
    var undefined;
    var mocks = {
        api: {
            login: {
                GET: function () { }
            }
        }
    };
});

In the first two specs above, I defined clearly what the isAuthenticated() method does, and since this was already implemented, they passed. But the real turning point was when it was time to spec out the service’s ability to determine whether a route was anonymous. I wrote the next two specifications as implementation-agnostic descriptions of what I was actually after.

it("indicates when a route is anonymous", function () {
});
 
it("indicates when a route is not anonymous", function () {
});

Once that was outside of my head and staring back at me in plain text, it occurred to me that that if block from the opening of this post–

if (!isAuthenticated && !(route.settings && route.settings.anonymous))

–yes, that one, was just trying too hard. The last two-thirds of it were an attempt to solve the anonymous route problem I had just finished specifying. And the fact that they weren’t isolated in their own named and testable function was what made it so difficult for me to quickly ascertain my code’s validity.

I descended on the keys with fresh purpose, spelling out the anonymous route checking functionality, as it should look like from the outside.

it("indicates when a route is anonymous", function () {
    var a = new auth(mocks.api);
    expect(a.isAnonymous(mocks.routes.signin)).toBe(true);
});
 
it("indicates when a route is not anonymous", function () {
    var a = new auth(mocks.api);
    expect(a.isAnonymous(mocks.routes.dashboard)).toBe(false);
    expect(a.isAnonymous(mocks.routes.simple)).toBe(false);
});
 
var mocks = {
    api: {
        login: {
            GET: function () { }
        }
    }, routes: {
        signin: { route: 'signin', moduleId: 'viewmodels/signin', settings: { anonymous: true } },
        dashboard: { route: '', moduleId: 'viewmodels/dashboard', settings: { anonymous: false } },
        simple: { route: 'simple/route', moduleId: 'viewmodels/simple/route' }
    }
};

Naturally, these tests went red in my test runner, because the isAnonymous(route) function didn’t exist yet. But it soon did.

function isAnonymous(route) {
    return route.settings && route.settings.anonymous;
}

Then, the tests went–red. Wait, still red? Yep.

auth-spec-fail

I was close, but I may never have picked out the distinction between undefined and false without that spec in place to guide me. I updated the function, making it a bit more specific in the truthy/falsy department.

function isAnonymous(route) {
    return (route.settings != undefined && route.settings.anonymous);
}

Boom. Greensville.

Finally, now that this irritating fruit fly of logic was labeled and tested, that code in guardRoute could now be read more organically, too.

if (!isAuthenticated && !isAnonymous(route))

When I read it aloud again in almost-English, I picked out a further subtlety: I really should test the route for non-anonymity first. I mean, if we’re hitting an anonymous route, our authentication status really doesn’t matter, and we shouldn’t waste any cycles checking on it.

if (!isAnonymous(route) && !isAuthenticated)

I like to imagine that Sam Clemens himself wouldn’t have put it more succinctly.

We think in English. The terse syntax of our favorite programming language becomes more compatible with the way we actually think when we use our words to specify and test our code. And offloading these micro-requirements from our heads into a self-testing suite of specifications doesn’t just make our software more readable and reliable, it also frees us up to focus on more important things.

Playing Jedi with the little guy, for instance.


  1. It’s a fair assumption that if you’re reading this in English, you’re thinking in English. If that’s not the case, by all means mentally drop your preference in whenever I mention it. Unless your preference is Klingon, in which case you must stop reading altogether and figure out where things went wrong for you.
Advertisements

One thought on “How Behavior-Driven Development Saved My Code from the Jedi Interruption

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s