If we look at the developments in the software testing area, step beyond in evolution after TDD is, so called, BDD (Behavior Deriven Development). It is hard to describe BDD in couple of sentences. Excerpt taken from Wikipedia says:
BDD focuses on obtaining a clear understanding of desired software behaviour through discussion with stakeholders. It extends TDD by writing test cases in a natural language that non-programmers can read. Behavior-driven developers use their native language to describe the purpose and benefit of their code. This allows the developers to focus on why the code should be created, rather than the technical details, and minimizes translation between the technical language in which the code is written and the domain language spoken by the business, users, stakeholders, project management, etc.
If this looks a bit complicated, in one sentence it would mean: BDD helps programmers and non-technical stakeholders understand each other better and develop software which will be the most representative model of the problem solved by writing the software itself.
Jasmine is a library for BDD testing of your JavaScript. It offers range of possibilities to describe software functionalities by simple and descriptive tests, which is one of the basic principles of BDD based development. This way tests usually serve as the most accurate software documentation. More about Jasmine library on the GitHub repository.
After you download standalone Jasmine distribution from here you’ll find in the archive all you need to write your first tests together with the example of your code organization. The most important fie is SpecRunner.html which, when run in browser, will execute tests and give you report about the state of the tests. SpecRunner.html is very simple and self explanatory. lib folder contains Jasmine library, src folder contains code to be tested and spec folder contains actual software specifications as tests.
Further, I will implement simple object that can register multiple callback functions and call them with given data. Something like simple observer. I will always first write tests and then implement functionality to make tests pass.
We start from the downloaded archive for standalone Jasmine library. We need to delete example code provided with the library to implement our own example.
cd jasmine-standalone # folder in which we extracted downloaded archive rm src/* rm spec/*
Also, from SpecRunner.html file we delete references to just removed scripts. Therefore, part which includes them stays empty like this:
... <!-- include source files here... --> <!-- include spec files here... --> ...
In TDD and BDD philosophy one rule is essential for success.
First you write the test which will at the beginning fail, then you implement code which should satisfy specification described by test. Then, you execute test and continue with implementation until test passes. When that happens you know that implementation is OK.
According to this we create file to place our tests in spec/ObserverSpec.js and we define first test to simply check existence of the observer object.
describe("rs.ji.observer", function () {
it("should be present in the global namespace", function () {
expect(rs.ji.observer).toBeDefined();
});
});
And we add this specification to SpecRunner.html to execute it:
<!-- include spec files here... --> <script type="text/javascript" src="spec/ObserverSpec.js"></script>
If we now open SpecRunner.html in some browser (i.e. Google Chrome) we will see one failing test.
It is important to take a look in how our test looks like. If you read the test you’ll see how descriptive it is and how with simple words expresses how our code should behave, although example is very simple. Here it takes some creativity of the developer to make tests really valuable but more than that is important that developer have deep understanding of the dictionary and terminology of the domain for which software is developed because understanding of the domain should be heavily embedded in the test definitions.
Next step is to implement the code which will satisfy test. We will create file src/observer.js
// defining so called namespace objects so I can namespace observer into
// rs.ji.observer
var rs = {};
rs.ji = {};
// observer implementation
rs.ji.observer = {};
And we will add it to SpecRunner.html:
<!-- include source files here... --> <script type="text/javascript" src="src/observer.js"></script>
If we run SpecRunner.html again, we can see that our test passes, it is green ![]()
Now, when we know for the principle “First failing test, then implementation” we can add new functionalities to our simple example. We add next test:
it("should be able to register callback function", function () {
// define function to be placed into the observer register
function callback() { return null; }
rs.ji.observer.register(callback);
var callbacks = rs.ji.observer.getCallbacks();
expect(callbacks.length).toBe(1);
expect(callbacks[0]).toBe(callback);
});
And implementation that makes this test pass:
rs.ji.observer = {
callbacks: [],
register: function (callback) {
this.callbacks.push(callback);
},
getCallbacks: function () {
return this.callbacks;
}
};
We want to check if we can register multiple callbacks. This functionality should be supported already, but we write test to confirm this:
// before every test we reset array of callbacks
beforeEach(function () {
rs.ji.observer.callbacks = [];
});
it("should be able to register multiple callbacks", function () {
function callback1() { return null; }
function callback2() { return null; }
rs.ji.observer.register(callback1);
rs.ji.observer.register(callback2);
var callbacks = rs.ji.observer.getCallbacks();
expect(callbacks.length).toBe(2);
expect(callbacks[0]).toBe(callback1);
expect(callbacks[1]).toBe(callback2);
});
Programmers often use print, echo and friends to test code for certain functionalities. That takes time as for writing a test, but from other side tests are possible to be executed all the time after this initial check is done even when we move to implement something else.
If we run SpecRunner.html in browser, all tests should pass.
Last thing our observer should do to satisfy its basic purpose is to update callback functions with some new information. That means it should be able to call them with some data. To define this functionality we write new test:
it("should provide update method to execute all callbacks with provided data", function () {
window.callback1 = function () { return null; }
window.callback2 = function () { return null; }
spyOn(window, 'callback1');
spyOn(window, 'callback2');
rs.ji.observer.register(window.callback1);
rs.ji.observer.register(window.callback2);
rs.ji.observer.update("data string");
expect(window.callback1).toHaveBeenCalledWith("data string");
expect(window.callback2).toHaveBeenCalledWith("data string");
});
Here we use some advanced techniques of spying on methods (SpyOn) in order to see if some method is called while other is executed.
As a helper, we will add jQuery library to src/jquery.js location and add it to SpecRunner.html file:
<!-- include source files here... --> <script type="text/javascript" src="src/jquery.js"></script> <script type="text/javascript" src="src/observer.js"></script>
And we implement code to satisfy the test:
// observer implementation
rs.ji.observer = {
callbacks: [],
register: function (callback) {
this.callbacks.push(callback);
},
getCallbacks: function () {
return this.callbacks;
},
// added update method to inform registered callbacks
// uses jQuery to iterate over the array
update: function(data) {
$.each(this.callbacks, function (i, callback) {
callback.call(null, data); // call every callback with provided data
});
}
};
That is it, now we have full implementation of simple observer with tests that can pretty clearly describe functionalities of the software we developed and they can as well prove in every time that needed functionalities are working OK by executing them in the browser (or different browsers to see if they will work in all of them).
Complete files:
describe("rs.ji.observer", function () {
beforeEach(function () {
rs.ji.observer.callbacks = [];
});
it("should be present in the global namespace", function () {
expect(rs.ji.observer).toBeDefined();
});
it("should be able to register callback function", function () {
// define anonymous function to be placed into the observer register
function callback() { return null; }
rs.ji.observer.register(callback);
var callbacks = rs.ji.observer.getCallbacks();
expect(callbacks.length).toBe(1);
expect(callbacks[0]).toBe(callback); // so we need to have first funtion in registered callbacks
});
it("should be able to register multiple callbacks", function () {
function callback1() { return null; }
function callback2() { return null; }
rs.ji.observer.register(callback1);
rs.ji.observer.register(callback2);
var callbacks = rs.ji.observer.getCallbacks();
expect(callbacks.length).toBe(2);
expect(callbacks[0]).toBe(callback1);
expect(callbacks[1]).toBe(callback2);
});
it("should provide update method to execute all callbacks with provided data", function () {
window.callback1 = function () { return null; }
window.callback2 = function () { return null; }
spyOn(window, 'callback1');
spyOn(window, 'callback2');
rs.ji.observer.register(window.callback1);
rs.ji.observer.register(window.callback2);
rs.ji.observer.update("data string");
expect(window.callback1).toHaveBeenCalledWith("data string");
expect(window.callback2).toHaveBeenCalledWith("data string");
});
});
// defining so called namespace objects so I can namespace observer into
// rs.ji.observer
var rs = {};
rs.ji = {};
// observer implementation
rs.ji.observer = {
callbacks: [],
register: function (callback) {
this.callbacks.push(callback);
},
getCallbacks: function () {
return this.callbacks;
},
update: function(data) {
$.each(this.callbacks, function (i, callback) {
callback.call(null, data);
});
}
};











