implements Elegance {

// Elwyn Malethan's musings on software development, mountain biking and general navel–gazing...

Articles published in category Software Development

If the users aren't motivated to help with software development...

If the users aren't motivated to help with software development by answering questions or reviewing the results of a sprint, then the software isn't creating any value. Stop work now and find something the users really want. — Stephen Lott

The number of times I‘ve wanted to do that! Some customers I‘ve worked with are really averse to engaging in the development process. However, it makes those customers that do eagerly engage in the process all the more a delight to work with. Guess which customers had most success with their projects…

First published on Feb 24, 2010. Last updated on: Feb 24, 2010.

JavaScript BDD framework in less than 200 lines

I‘ve been doing a lot of work with JavaScript lately and so I‘ve been thinking a lot about how to effectively bring my experiences of BDD and TDD in other technologies to the party. There are a few test frameworks available, JsTestDriver by far the better looking of the lot. There is a specification framework, based on rspec as well. However, I couldn't find a JavaScript BDD story framework anywhere. So armed with some new knowledge from JavaScript: The Good Parts, I had a go at looking into what such a framework would look like. I was surprised as to how quickly I came up with a solution and how small the footprint of the end result is.

So here it is, in less than 200 lines, a JavaScript BDD Framework I've called it BehaviourJS and I've made it available under the GNU Lesser General Public License.

// ------------------------------------------------------------ Function augmentation

/**
 * Standard means of augmenting everything with new methods
 *
 * @param name the name of the new method
 * @param func the function we want invoked
 */
Object.prototype.method = function(name, func) {
    if (!this.prototype[name]) {
        this.prototype[name] = func;
        return this;
    }
};

// ------------------------------------------------------------ Should DSL

behaviourJs = {};

behaviourJs._shouldBe = function(expected) {
    if(expected !== null) {
        var exp = expected.valueOf();
        var self = this.valueOf();
        if(self === exp) {
            return;
        }
    }
    throw {
        name: 'AssertionError',
        message: 'Expected ' + expected + ' but was ' + this
    };
};

Object.method('shouldBe', behaviourJs._shouldBe);
Object.method('shouldEqual', behaviourJs._shouldBe);
Object.method('shouldBeEqual', behaviourJs._shouldBe);
Object.method('shouldBeEqualTo', behaviourJs._shouldBe);

behaviourJs._shouldNotBe = function(expected) {
    if(expected !== null) {
        var exp = expected.valueOf();
        var self = this.valueOf();
        if (self !== exp) {
            return;
        }
        throw {
            name: 'AssertionError',
            message: 'Expected not to be ' + expected + ' but was ' + this
        };
    }
};

Object.method('shouldNotBe', behaviourJs._shouldNotBe);
Object.method('shouldNotEqual', behaviourJs._shouldNotBe);
Object.method('shouldntBe', behaviourJs._shouldNotBe);
Object.method('shouldntEqual', behaviourJs._shouldNotBe);

// ------------------------------------------------------------ StoryInstance

behaviourJs.StoryInstance = function(block) {
    block.call(this);
};

behaviourJs.StoryInstance.prototype.visit = function(visitor) {
    visitor.visitStory(this);
};

behaviourJs.StoryInstance.prototype.run = function() {
    this.visit(new behaviourJs.StoryRunner());
};

// ------------------------------------------------------------ ScenarioInstance

/**
 * A runnable scenario.
 *
 * @param name the description of the scenario
 * @param block the function containing the contents of the scenario (usually
 * a series of Givens,When and Thens).
 */
behaviourJs.ScenarioInstance = function(name, block) {
    this.name = name;
    this._steps = [];

    behaviourJs.ScenarioInstance.__currentScenario = this;

    block.call();

    behaviourJs.ScenarioInstance.__currentScenario = null;
    behaviourJs.ScenarioInstance.__currentPhase = null;
};

behaviourJs.ScenarioInstance.__currentScenario = null;

behaviourJs.ScenarioInstance.__currentPhase = null;

behaviourJs.ScenarioInstance.prototype.addStep = function(step) {
    this._steps[this._steps.length] = step;
};

behaviourJs.ScenarioInstance.prototype.visit = function(visitor) {
    visitor.visitScenario(this);
};

// ------------------------------------------------------------ Step

behaviourJs.Step = function(type, meta, block) {
    this._type = type;
    this._meta = meta;
    this._block = block;
};

behaviourJs.Step.prototype.visit = function(visitor) {
    visitor.visitStep(this);
};

// ------------------------------------------------------------ StoryRunner

behaviourJs.StoryRunner = function() {
    this.storyCtx = {};
};

behaviourJs.StoryRunner.prototype.visitStory = function(story) {
    var title = 'Story: ' + story.description;
    document.writeln(title);
    var line = '';
    for(var i = 0; i < title.length; ++i) {
        line += '=';
    }
    document.writeln(line);
    for (var scenIndex = 0; scenIndex < story.scenarios.length; ++scenIndex) {
        story.scenarios[scenIndex].visit(this);
    }
};

behaviourJs.StoryRunner.prototype.visitScenario = function(scenario) {
    document.writeln('Scenario: ' + scenario.name);
    for (var stepIndex = 0; stepIndex < scenario._steps.length; ++stepIndex) {
        var step = scenario._steps[stepIndex];
        step.visit(this);
    }
    document.write("\n");
};

behaviourJs.StoryRunner.prototype.visitStep = function(step) {
    var stepDesc = step._type.toUpperCase() + ' ' + step._meta;
    if (step._block === undefined || step._block === null) {
        if (step._type === 'then') {
            stepDesc += ' [PENDING]';
        }
    } else {
        try {
            step._block.call(this.storyCtx);
        } catch(e) {
            if (e.name == 'AssertionError') {
                stepDesc += ' [FAIL : ' + e.message + ']';
            } else {
                stepDesc += ' [ERROR (' + e.name + ') : ' + e.message + ']';
            }
        }
    }
    document.writeln(stepDesc);
};

// ------------------------------------------------------------ DSL

Story = function(block) {
    return new behaviourJs.StoryInstance(block);
};

Scenario = function(name, block) {
    return new behaviourJs.ScenarioInstance(name, block);
};

Given = function(desc, block) {
    var scenario = behaviourJs.ScenarioInstance.__currentScenario;
    behaviourJs.ScenarioInstance.__currentPhase = 'given';

    scenario.addStep(new behaviourJs.Step(behaviourJs.ScenarioInstance.__currentPhase, desc, block));
};

When = function(desc, block) {
    var scenario = behaviourJs.ScenarioInstance.__currentScenario;
    behaviourJs.ScenarioInstance.__currentPhase = 'when';

    scenario.addStep(new behaviourJs.Step(behaviourJs.ScenarioInstance.__currentPhase, desc, block));
};

Then = function(desc, block) {
    var scenario = behaviourJs.ScenarioInstance.__currentScenario;
    behaviourJs.ScenarioInstance.__currentPhase = 'then';

    scenario.addStep(new behaviourJs.Step(behaviourJs.ScenarioInstance.__currentPhase, desc, block));
};

And = function(desc, block) {
    var scenario = behaviourJs.ScenarioInstance.__currentScenario;
    scenario.addStep(new behaviourJs.Step(behaviourJs.ScenarioInstance.__currentPhase, desc, block));
};

My approach is heavily influenced by my experiences of using Easyb for BDD for Java. There are a few things I haven‘t added, such as before and after hooks.

Stacktrace support in JavaScript is sketchy and differs greatly across runtimes/browsers. One solution might be to integrate this attempt at universally obtaining a stacktrace. The should DSL is not as comprehensive as Easyb. Unlike Easyb the should DSL in BehaviourJS is completely extensible. This is possible because of the nature of the JavaScript langauge.

I mentioned runtimes/browsers earlier. The other potential issue is that I have only tested this in Firefox. It‘s the product of a couple of hours hacking so it‘ll be no surprise if it fails in other browsers.

Runners, build system and IDE support.

I realise that just implementing such a framework isn‘t even half of the effort required for a test framework to enable developers to practice TDD or BDD. For this framework to be useful to me or anyone else, it will need IDE support (IDEA and Eclipse at least) and build system support (Maven and maybe Ant).

The existing JavaScript test frameworks have had a lot of work put into this area and it shows. Especially with JsTestDriver‘s IDE support, which is awesome.

Maybe next weekend I‘ll have a look at plugin this into an existing test framework, such as JsTestDriver.

Example story

A story using the above framework might look something like this.

Story(function() {
    this.description = "This is an example story";
    this.summary = {
        as_a : "role",
        i_want : "to perform some action",
        so_that : "there is some perceived benefit"
    };

    this.scenarios = [
        Scenario("Some scenario", function() {
            Given("some string", function() {
                this.someString = 'this';
            });
            And("some number", function() {
                this.ten = 10;
            });
            When("something happens");
            Then("some condition is evaluated", function() {
                this.someString.shouldNotBe(null);
                this.someString.shouldNotBe({});
                this.someString.shouldNotBe(123);
                this.someString.shouldNotBe('that');

                this.someString.shouldBe(this.someString);
                this.someString.shouldBe('this');
            });
            And("some other condition is evaluated", function () {
                this.ten.shouldBe(10);
            });
        }),
        Scenario("Checking incorect values", function() {
            Given("some new precondition");
            And("some other new precondition");
            When("something new happens", function() {
                this.animal = 'animal';
            });
            Then("some new condition is evaluated", function() {
                try {
                    thisVar.doesNotExist();
                } catch(e) {
                    // gulp
                }
            });
            And("some other new condition is evaluated", function() {
                try {
                    this.animal.shouldBe('farm');
                } catch(e) {
                    e.name.shouldBe('AssertionError');
                }
            });
        }),
        Scenario('some complex objects', function() {
            Given(' a complex object', function() {
                this.MyThing = function(val) {
                    this._val = val;
                };

                this.someObj = new this.MyThing('a value');
            });
            And('a copy of it', function() {
                this.sameObj = this.someObj;
            });
            And('a different object', function() {
                this.differentObj = new this.MyThing('a value');
            });
            Then('the object and the copy should be the equal', function() {
                this.someObj.shouldBe(this.sameObj);
                this.sameObj.shouldBe(this.someObj);
            });
            And('the object and the different one should not', function() {
                this.someObj.shouldNotBe(this.differentObj);
                this.differentObj.shouldNotBe(this.someObj);
            });
        })
    ];
}).run();

The link to JavaScript: The Good Parts above is an affiliate link. It's a great book, written by a programmer for programmers.

First published on Jan 24, 2010. Last updated on: Jan 25, 2010.

Why TDD: Freedom to refactor

Dave and Jen work for software companies that produce exciting and clever software products. Both Dave and Jen understand design patterns. They both know about encapsulation, inheritance, composition and all that jazz. They‘re both good software developers. The only difference is that Jen has taught herself to develop test-driven. Dave thinks his time is better spent writing production code.

Dave and Jen started their current project at the same time. Dave spent some time up–front to carefully design his architecture to the requirements and implemented all the features. Dave added some unit tests, but only as an afterthought. Dave is confident that his architecture is well suited to providing a maintainable solution to the requirements specified. Dave is OK with the fact he has less than 25% test coverage.

Jen used TDD, she started off by adding some tests to represent some of the requirements and implementing production code to pass those tests. She did this iteratively and in small digestible chunks until she had satisfied all of the requirements. Jen changed the architecture of the project frequently. She refactored as she went, renaming classes and methods so that they better represented their function, moving code and responsibilities between classes to continually improve the design of the software. She did this safe in the knowledge that her tests would pick up any errors introduced. Jen builds up test coverage of over 80%.

As always happens in a software project, the requirements change. In Dave‘s case, management decided that – for political reasons – Dave needed to completely change the way his product worked. Jen‘s customer‘s priorities changed. To stay competitive, they needed to have different features. In both cases – as ever – time was short and deadlines were tight.

Both Jen and Dave found that their existing design wasn‘t really suited to the new requirements. Dave was concerned about the deadline and the time it would take to evaluate the correctness of any change he made in the design of the software. He has no timely or repeatable means of doing this. So Dave added the feature without changing the design. He knew it wasn‘t ideal and that the he would need to change the architecture at some point in the future. Given the time constraints, Dave believed he was being pragmatic.

Jen – on the other hand – had been refactoring the code extensively throughout the development of the project. Jen recognises an alternative design that would elegantly accommodate this new requirement and refactors. She runs the tests to see if she broke something. A few of them fail. So she fixes them and runs the tests again, safe in the knowledge that her tests represent the requirements.

Over the next few years a number of new features are requested by both Dave‘s and Jen‘s customer(s). Each time, Dave opts for the change that least affects the existing code-base. Each time Dave‘ project becomes more like a ball of mud. Dave becomes more and more resistant to new features. He started asking management for some “engineering time“ to refactor the code and have the time to evaluate it‘s correctness. This – he knew – would take some time.

In the same time, Jen‘s team have been doing nothing but implementing new features. The design of the software has changed frequently in that time. She doesn‘t need any “engineering time”, her tests evaluate the correctness of every feature she adds.

Dave has become scared of changing the code, he doesn‘t know what he might break.

Jen breaks the code every day. She‘ll know in seconds what she‘s broken and what she needs to do to fix it.

The design patterns link above is an affiliate link. Any and every software developer should have a well–thumbed copy of this book.

First published on Jan 10, 2010. Last updated on: Jan 10, 2010.

TDD in JavaScript intensive software

I recently switched jobs. I left Smartstream for somewhere closer to home. I did this primarily to avoid the commute from Cardiff to Aztec West, in Bristol. I'll be glad to avoid that drive on the M4 every day. I joined Move, who are based in Utah but have a development office not too far from Cardiff.

Having been working in the familiar world of server–side Java before Christmas, I‘ve now moved to the team that work on what we call the “middleware”. Primarily, this is the client software that runs on set–top boxes or web browsers. This is therefore almost entirely JavaScript.

Developing software that is primarily JavaScript is a new experience for me and one I‘m relishing, if with some trepidation. After a bit of Googling, I‘ve basically concluded that there isn‘t vast support for TDD for JavaScript development. This is especially true of IDE support. One thing that does look promising is JsTestDriver, though I‘ve been having trouble getting it to work reliably. This is most likely lack of familiarity with both JavaScript and JsTestDriver.

This is challenging stuff, but that‘s great as far as I‘m concerned.

First published on Jan 5, 2010. Last updated on: Jan 5, 2010.

Taking the fear out of change: BDD is essential

During the lifetime of a project, requirements are going to change. The world moves quickly, customers change their minds. You‘re just going to have to deal with it .

So, do you choose a development methodology that tries to manage or contain that change or do you accept the fact that it‘s going to happen and embrace that change?

There are two things I always keep in mind when working on a software project. These are Kent Beck's you ain't gonna need it and Rory Marinich's be less shit.

Keep the requirements brief and focussed: only enough for 2–3 weeks of work. Few people have the attention span for more. Deliver only what you know you need and then revise according to the inevitable evolution in priorities — eradicate any shortcomings, de–shit. The only software development model that supports this is one that repeatedly releases software in short iterations.

It also has to be one that takes the fear out of change. A really good way to do that is to have the security blanket of high unit–test coverage. A good way to get high unit–test coverage is to TATFT. An automatic way to TATFT is let the requirements drive and shape your code by using BDD.

So, embrace change, test–first, use BDD and TATFT.

First published on Aug 17, 2009. Last updated on: Dec 29, 2009.

Software Engineering is not dead

Software Engineering is not dead, it has never lived! There has been a lot of controversy about that, some people arguing it's more of an art, others saying it's science, yet others that it's more like craftsmanship. At the end of the day, the only fact that there is a controversy shows that it doesn't fit in the hole we're trying to stick it into. And yes, this blog is called Software Artist, but I have to tell you that it's more as a reaction to those who are fiercely trying to 'industrialize IT processes'. Oh man, that makes me angry! In the end, I have to say that the more I think about it, the more I like the idea of craftsmanship.

Because craftsmanship is all about finding the right balance between the techniques that you've learnt and the intuition that you have about what you're doing. And finding this right balance requires a lot of practice.

First published on Jul 23, 2009. Last updated on: Dec 29, 2009.

 
People I like
Other sites