How to bugfix in a BDD way

Developing new features using BDD is a very clean process. Our tests are always driven by user requirements. We split these requirements into several levels using nested contexts. We organize them into a neat structure, resembling almost end-user docs – and telling the coherent story of our system.

Unfortunately, things tend to become less tidy when bugfixing.

Don’t let the bug fixes make your specification incoherent

When fixing a bug, our focus is at a much lower level. We start with a very specific description of a problem and try to debug it to find the root cause. In effect, we usually have a more detail-oriented, out-of-context point of view than when we start with the high-level requirements. As a result, we tend to write similarly detail-oriented tests.

However, we shouldn’t forget that bugs never exist in a vacuum. A bug is either a missing requirement or a hole in our implementation of an existing one. There is no such a thing like a “standalone” bug.

Therefore, we should NEVER treat bug fixes nor the tests for these bug fixes as standalone patches. When fixing a bug, we should always search for a way to accommodate the missing test into existing specification so that it remains coherent.

Let’s see this idea in action:

An existing system

We’re coding a dashboard for a spaceship. A typical trade-off on such a ship is the distribution of power between various subsystems. Our plasma cannons and shields are so power hungry that we can’t run our propulsion drive at more than 30% of its max efficiency when they’re on.

Of course, controlling all the systems separately is inconvenient, so we add a nice switch to our dashboard that automatically sets the power generator into one of two modes: a battle mode (with weapons on and propulsion drive capped at a 30%) and a cruise mode (with weapons off and drive uncapped).

Let’s see a relevant part of the tests for our dashboard:

describe('Power Generator', function() {
    describe('in battle mode', function() {
        beforeEach(function() {
            generator.setBattleMode();
        });             

        it('unlocks weapon systems', function() {
            expect(plasmaCannons).toBeUnlocked();
            expect(shields).toBeUnlocked();
        });

        it('caps propulsion drive power', function() {
            expect(propulsion.powerCap()).toEqual(propulsion.maxPower() * 0.3);
        });
    });

    describe('in cruise mode', function() {
        beforeEach(function() {
            generator.setCruiseMode();
        });     

        it('locks weapon systems', function() {
            expect(plasmaCannons).toBeLocked();
            expect(shields).toBeLocked();
        });

        it('allows max power to propulsion drive', function() {
            expect(propulsion.powerCap()).toEqual(propulsion.maxPower());
        });
    }); 
});

The tests pass and we set off to cross the endless void of the universe.

Unfortunately, during our first encounter with the space pirates, we discover a bug.

A bug

Our gunner likes to shoot plasma beams to the sound of an epic battle music. He pumps up the speakers to the max, and… our shields suddenly go down! We barely make it alive.

We check our system with a debugger and it turns out that our new audio system is much more power greedy than we thought. It can’t be used together with shields.

A naive bugfix

Because we’re in the context of all the low-level details, it’s easy to fall into the trap and “fix” the tests suite in a way matching exactly the description of a problem we’ve just discovered:

describe('Power Generator', function() {
    describe('in battle mode', ...);

    describe('in cruise mode', ...);  

    it('locks speakers when the shield is on', function() {
        shield.turnOn();
        expect(speakers).toBeLocked();
    });
});

Although such a test allows us to fix the bug, it seems completely out of place. It doesn’t tell a coherent story together with the rest of the suite.

A clean bugfix

If we consider it for a moment, we’ll see that locking the speakers shouldn’t be directly related to the shields, but is a part of a more general, higher-level use case (that is already present in our specification):

describe('Power Generator', function() {
    describe('in battle mode', function() {
        beforeEach(function() {
            generator.setBattleMode();
        });             

        it('unlocks weapon systems', ...);

        it('caps propulsion drive power', ...);

        it('locks speakers', function() {
            expect(speakers).toBeLocked();
        });
    });

    describe('in cruise mode', function() {
        beforeEach(function() {
            generator.setCruiseMode();
        });     

        it('locks weapon systems', ...);

        it('allows max power to propulsion drive', ...);

        it('unlocks speakers', function() {
            expect(speakers).toBeUnlocked();
        });
    }); 
});

Now everything clicks into its proper place, forming a consistent specification.

A coherent story, not a random list of exceptions

In the above example it’s easy to spot the problem, so it may seem a bit artificial. However, in real code, such misplaced tests don’t always stand out so clearly. If we don’t take an active effort to find and properly consolidate them, our suite degenerates over time, turning from a tidy specification into a random list of various edge cases and exceptions.

Such a suite definitely works and catches regression errors, but it doesn’t tell the coherent story, making the understanding of the system unnecessarily hard.

How about you? Have you ever encountered a similar problem when bugfixing using BDD process? Please share below in the comments!

Advertisements

What do you think?

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