Behavior-Driven Testing

Last edited on

Behavior-Driven Testing (BDT) defines user stories in a human-readable given-when-then format. This testing approach is typically used as part of Behavior-Driven Development (BDD) or other agile development efforts.

To demonstrate the usage of BDT with Squish we have implemented a JavaScript framework that is based on the de-facto standard input language Gherkin. This article demonstrates the usage of this framework which is available on request.

Input

The addressbook.feature file describes the expected behavior of the Addressbook application when addressbooks are being created and filled. It contains two different scenarios with Given, When and Then steps.

addressbook.feature

Feature: Filling of addressbook

Scenario: Initial state of created addressbook

Given no prior existing addressbook

When I create a new addressbook

Then no entries should be present

Scenario: State after adding first entry

Given a newly created addressbok

When entering the first person

Then one entry should be present

Above feature file is placed inside of the tst_addressbook test case of the behavior-driven development test suite suite_bdd. As a preparation of the test case implementation the following squishrunner execution will generate a test script skeleton:

$ FEATURE=addressbook squishrunner --testsuite suite_bdd --testcase tst_generator

Test Skeleton

The test.js skeleton looks like this:

source(findFile("scripts", "Feature.js"));

var feature = new Feature("addressbook.feature");

// Scenario 1
var scenario1 = feature.addScenario("Initial state of created addressbook");

scenario1.Given("no prior existing addressbook", function () {
    test.log("Implement me!");
})

scenario1.When("I create a new addressbook", function () {
    test.log("Implement me!");
})

scenario1.Then("no entries should be present", function () {
    test.log("Implement me!");
})

// Scenario 2
var scenario2 = feature.addScenario("State after adding first entry");

scenario2.Given("a newly created addressbok", function () {
    test.log("Implement me!");
})

scenario2.When("entering the first person", function () {
    test.log("Implement me!");
})

scenario2.Then("one entry should be present", function () {
    test.log("Implement me!");
})

function main() {
   feature.execute();
}

The test.log() statements denote the functions that still need to be filled with calls controlling the application to be tested.

Test Implementation

Via the use of an Application convenience class the implementation of the functions is a matter of adding 1-3 lines each:

source(findFile("scripts", "Feature.js"));
source(findFile("scripts", "Application.js"));

var feature = new Feature("addressbook.feature");

// Scenario 1
var scenario1 = feature.addScenario("Initial state of created addressbook");

scenario1.Given("no prior existing addressbook", function () {
    this.scenario.application = new Application();
    this.scenario.application.launch();
})

scenario1.When("I create a new addressbook", function () {
    this.scenario.application.createAddressbook();
})

scenario1.Then("no entries should be present", function () {
    test.compare(this.scenario.application.countEntries(), 0);
})

// Scenario 2
var scenario2 = feature.addScenario("State after adding first entry");

scenario2.Given("a newly created addressbok", function () {
    this.scenario.application = new Application();
    this.scenario.application.launch();
    this.scenario.application.createAddressbook();
})

scenario2.When("entering the first person", function () {
    this.scenario.application.addDummyEntry();
})

scenario2.Then("one entry should be present", function () {
    test.compare(this.scenario.application.countEntries(), 1);
})

function main() {
   feature.execute();
}

Helper Object

The aforementioned Application class has been created for convenience based on a recording created with Squish. The class wraps around the Application Under Test and can be shared among test cases. The functions Application.launch(), createAddressbook(), addDummyEntry() and countEntries() are defined like this:

import * as names from 'names.js'
function Application() {
    this.launch = function() {
        startApplication("AddressBook.class");
    }
    this.createAddressbook = function() {
        activateItem(waitForObjectItem(names.AddressBook_JMenuBar,
				                   "File"));
        activateItem(waitForObjectItem(names.File_JMenu, "New..."));
    }
    this.addDummyEntry = function() {
        activateItem(waitForObjectItem(
                     names.AddressBook_JMenuBar, "Edit"));
        activateItem(waitForObjectItem(names.Edit_JMenu, "Add..."));
        type(waitForObject(names.AddressBook_AddForename_JTextField),
                           "Mike");
        type(waitForObject(names.AddressBook_AddForename_JTextField),
                           "<Tab>");
        type(waitForObject(names.AddressBook_AddSurname_JTextField),
                           "Meyers");
        type(waitForObject(names.AddressBook_AddSurname_JTextField),
                           "<Tab>");
        type(waitForObject(names.AddressBook_AddEmail_JTextField),
                           "mike@example.com");
        type(waitForObject(names.AddressBook_AddEmail_JTextField),
                           "<Tab>");
        type(waitForObject(names.AddressBook_AddPhone_JTextField),
                           "0123/456789");
        clickButton(waitForObject(names.AddressBook_AddOK_JButton));
    }
    this.countEntries = function() {
        return findObject(names.AddressBook_JTable).rowcount;
    }
}

Execution

Now, the behavior-driven test is ready to be executed. It can either be done from the Squish IDE or the command line:

$ squishrunner --testsuite suite_bdd --testcase tst_addressbook

Here is the log of the run:

START_TEST_CASE Start 'tst_addressbook'         Test Case 'tst_addressbook' started
LOG            	shared/scripts/Feature.js:50: 	Executing 2 test(s) for 'addressbook.feature'
LOG             shared/scripts/Feature.js:11: 	Executing scenario 'Initial state of created addressbook'
LOG             shared/scripts/Feature.js:27: 	Executing step: Given no prior existing addressbook
LOG		shared/scripts/Feature.js:27:	Executing step: When I create a new addressbook
LOG		shared/scripts/Feature.js:27:	Executing step: Then no entries should be present
PASS		tst_addressbook/test.js:19: 	Comparison       '0' and '0' are equal
LOG           	shared/scripts/Feature.js:11:	Executing scenario 'State after adding first entry'
LOG             shared/scripts/Feature.js:27: 	Executing step: Given a newly created addressbok
LOG            	shared/scripts/Feature.js:27:	Executing step: When entering the first person
LOG             shared/scripts/Feature.js:27:	Executing step: Then one entry should be present
PASS           	tst_addressbook/test.js:36: 	Comparison       '1' and '1' are equal
END_TEST_CASE   End 'tst_addressbook'           End of test case 'tst_addressbook'

And here is the result summary:
*******************************************************
Summary:
Number of Test Cases:	1
Number of Tests:          	2
Number of Errors:          	0
Number of Fatals:          	0
Number of Fails:           	0
Number of Passes:          		2
Number of Expected Fails:          	0
Number of Unexpected Passes:      	0
Number of Warnings:        		0
*******************************************************

Outlook

Possible extensions of the framework: