In this article I will try to build upon the concept of Behavior Driven Development(BDD), using the JavaScript testing framework Jasmine. As we already know, JavaScript had a long way down the road, becoming from a simple scripting language, for the world wide web, a full stack development language.
Because of this reason it happens, that we have, sometimes undesired, a migration of the business logic from back-end to front-end. This adds a new level of complexity on our client-side layer, therefore this layer will have more responsibilities. What I want to address here, is that once we have more responsibilities on client-side, the project›s maintenance costs are directly impacted. It had been demonstrated scientifically that 75% of the project›s road-map is the maintenance concern, and 25% stands for the actual development [1]. Therefore, besides the performance and scalability factors of our application, we should address another one, the maintenance concern. BDD helps us building a decoupled, robust and easily adaptable-for-future-changes system.
Practically if we ask 10 developers to explain BDD they will come up with 10 different answers.
Some might say BDD is just TDD, but done well, some might say BDD is a second generation of Agile methodology, others might say BDD is an elegant technique for expressing executable specifications, and the list might go on.
Because BDD comes as an augmentation of TDD, let›s say some words about the traditional methodology TDD. Programmers which do TDD have reached the conclusion that the only things in common TDD has with tests, is the keyword "test", and nothing more. It might sound weird right now, but stay on track, because I plan to scatter the fog.
The basic steps any developer discovers when it does TDD are the following:
I am about to describe the last point in detail.
Sadly most developers cannot step over the second point.
Dan North (the founder of BDD) described BDD to be a methodology that helps us implementing the application by describing its behavior from the perspective of its stakeholders.
This short and concise description can be divided into more sections, which I am about to describe.
Over time, probably any of us have been constrained by the downsides of the push-based methodology. In short the "push-based" describes those primitive times, when the manager spread some tasks over a bunch of developers, saying: "You should finish this task by the end of the week". We couldn›t unleash our skills, or willing to improve ourselves on a specific domain, because we were constrained to solve those handed tasks, therefore we couldn›t gain any new experiences over other variety of technologies.
In BDD we have a backlog, a queue in which tasks are pushed. It is something like the Producer-Consumer Pattern, where every developer acts as a consumer, consuming/resolving some pulled tasks, and the stakeholder acts as the Producer, which pushes new features on the stack.
This approach improves the communication between the developer and the stakeholder on one side, because any incoming requirement is a high level feature, exposed from the stakeholder point of view, deprived from any technical detail. Afterwards, the developer divides this feature into sub-tasks, which are then prioritized by business value. This approach eliminates the possibility of implementing something that is not part of the stakeholder requirement, and in the same time, an ubiquitous language is born.
As known, writing the test-first-code we ask ourselves, from the beginning, what is exactly that we need to make this failing test pass. By writing tests first, we will write just enough code to make our failing test pass, nothing more. In this context by doing TDD we avoid doing Big Design Up Front (BDUF). We will be the first users of our API. As Kent Beck said in his famous book "TDD By Example" [3], we should place our thoughts into how we would like to interact with our API, and we start writing the test based on this assertion.
Writing the production code first, and then the covering tests, most of the time, we will not focus on designing our production code to be testable from the start. Following this course we will end up coding something fragile, tightly coupled with a lot of dependencies, hence immobile and not reusable. If the design is not testable, that means it is hard to be verified, and the loop ends having an untestable code, therefore unreliable.
How many times has it happened that we change something in this so called "isolated" part, and break other dependent components? I must say it has happened to me...
In the end, tests should be considered as a safety net under any serious refactoring, and any change in business logic. It is important to notice that we should test the business logic, that is the behavior, and not trivial operations such as getters and setters, or other third party APIs which come bundled with their tests.
Now the difference between TDD and BDD, is that TDD tells us we have a test involved, while BDD, tells us we have a more meaningful word behavior. First thoughts are that a test is supposed to assert something that is true or false, while behavior describes a more meaningful interpretation of our domain logic. In BDD the form of the test is replaced by a specification. What both TDD and BDD share in common is that we end up having executable specifications, which will serve in the future as living documentation.
I want to explain a little bit about the words: "living documentation". Most surely it happened to us to have a static specification (high level specification), in the form of a document or user story, which describes the features of our application. Now this specification combined with the developer›s specifications exposed through testable scenarios violate the Don›t Repeat Yourself (DRY) principle in its pure sense, because there is a duplicated knowledge in there. When something changes, the application behavior needs to be updated, while the text-documentation might not change, becoming deprecated. We should keep them in sync somehow. This introduces a new overhead in development.
BDD focuses on transporting this high-level specification into testable code, which proves more useful by verifying the sanity of our application. This executable specification goes along with our production code.
If there is an Outside In form of development, the intuition tells us, it should also be an Inside Out form as well, which I think all of us followed at some time. Let"s start with the latest one.
Doing Inside Out we start and implement some operations or functions, which we consider a core part of the requirement, and we start building upon them, adding others. It is easy to build a premature abstraction, which is simply infected and wrong.
Inside Out makes one thing hard and it makes it well. As the business logic becomes more complex, it will be hard to find the right-path that will guide us to implement the complex feature.
Often this form of development pushes us to develop code that will not be reusable, and obsolete.
This leads us to wasted time, and money, to develop something that ends up not being used. Extreme Programming emerged this into a principle of its own, called You Ain›t Gonna Need It (YAGNI) [4].
On the other hand, the Outside In form of development shares nothing with the former described method. In Outside In we start coding from a high-level spec, which happens to have business value.
Starting with a high-level specification, we code our way in, breaking the spec into more cohesive specifications, hence implementing just as much as it needs, and nothing more.
Coding in this form, we end up forced to behave like we already have some operations, which are not fully implemented. This can be one of the downsides of this development form, because we will not be able to run the tests against the functionality, until all the implementation is finished. This on one side beats the purpose of BDD/TDD, which states that we should run the tests as often as possible, so that we can catch early bugs.
In the same context, implementing the full functionality in one step is not considered to be quite a baby-step. The scope is to derive low level specs from high-level specifications. We can name the low-level specifications inner-circle, while the high-level specifications loop inside outer-circle.
The principle that is at the heart of this methodology is "divide et impera" [5]. It is far easier to solve a complex problem, by breaking it into a list of small and cohesive problems. These small solutions can compose the final result for our initial complex problem.
Implementing in a BDD manner, we might map this process as "One-To-Many" relationship. The "One" is the current subject under test (SUT), while the "Many" relation stands for its dependents, the SUT›s collaborators. SUT should behave as it already has its dependents in place, and not bother knowing about their implementation details. Even if its collaborators are not fully implemented, this postponeing will help gaining a better knowledge of the domain of SUT. Relying upon abstractions is always a good technique.
The structure of our executable specifications can be done by:
By Feature, it means the specifications are grouped by a topic of the requirement. One theoretical scenario might be to calculate the total items in a shopping cart (for e.g. an online shop). In this feature we might have several routines that communicate, sending messages to each other:
var shoppingCart = getShoppingCart(user)
var totalAmount = ItemCalculator.calculateTotal(shoppingCart)
var totalAmountWithVAT = Taxes.applyVAT(totalAmount)
We can clearly see that we have some operations that send messages to each other, helping calculate the total price of items in the cart. This can be mapped into specifications as one feature.
Grouping specifications by feature, yields easily in spaghetti code, when the feature changes over time, and needs to be updated, or new functionalities augment the initial feature. The scope is to keep our specifications as clean as possible. Specifications should have the same value as the production code.
On the other hand, structuring specifications by Fixture, means we end up with several "execution-contexts". We can have one execution-context when the shopping cart is empty, we can have another execution-context when the cart reached the maximum allowed amount. This approach of structuring the specifications drives to a clean and elegant design, with the operations grouped by the context in which they are executed.
The last one stands for Organizing the Specifications by Method. This approach is tedious, and can easily drive, as well, to a spaghetti code. Why is that? Because taken an operation "foo()", and "bar()", we have some tests written for "foo()". Internally "foo()" might use "bar()", for which we already have some verifications done. Therefore our specifications will seamlessly become redundant and other programmers might not take our "bullet-proof" tests seriously, therefore our tests would become deprecated, and obsolete in time.
BDD reveals the intention, through a well-known business language, which is a simple DSL, called GHERKIN [6]. This business language appeared for the first time in 2008 in another behavioral driven framework, called Cucumber [7]. It"s a business language because it does not point specifically to programmers, but it can be easily interpreted and understood by a non-programmer.
As we might know, TDD has its own Structuring-Pattern to write tests. This is also known as the Arrange Act Assert Pattern. These instructions help us structure our code within the body of the test, making it more readable and maintainable. Still, structuring our code does not imply that domain experts will understand our code.
On the other hand, GHERKIN improves the communication between the stakeholder and the developer, because it helps building an ubiquitous language between both worlds.
GHERKIN reveals a cleanest way of expressing the specification. The transported keywords are:
We can map this into a real requirement very easily, which can be interpreted by a non-programmer as well: "GIVEN the employees from a department, WHEN payday comes and computeSalary() is invoked, THEN I want to receive a salary report for all employees".
This information seems more useful, than having a test asserting something is true or false. It increases the expressiveness by using plain natural language to declare a real requirement. This is the business value that GHERKIN provides.
Let›s reiterate some of the advantages of BDD over the traditional TDD:
The Second Part of my article has as main actor the Jasmine [8] testing-framework. The initial scope was to build an EventBus library, developed following BDD methodology, using Jasmine as the "testing" framework, however the article has enlarged itself pretty much, therefore I prefer to describe some core parts of it, and let you check the code on github [9].
A few words about Jasmine. Jasmine is a unit testing framework, and not an integration framework, as some might think. It allows structuring the code by Fixture, and allows nested "describe() blocks". It also provides some common used routines for setup and teardown the execution context. It uses spies as the Test Double Pattern, and can be easily integrated in a continuous integration environment.
describe("EventBus - SUT", function () {
// various fixtures
it("should do this and that - describe behavior", function() {
// some executions
expect(thisResult).toBeTruthy();
});
});
Our Subject Under Test (SUT) here is the EventBus. As the name suggests, it is a data-bus, which is responsible for managing events and listeners.
I will not start another discussion about its benefits, but I prefer to say it drives to a loosely communication between collaborating objects. Its ancestor is the well-known Observer Design Pattern.
Instead of directly managing the dependencies, we delegate this responsibility to the EventBus, which knows about the events and the handlers. The good part is that none of the objects triggering the events will know something about their handlers. Events are simply published in this EventBus, which have or may not have corresponding registered handlers. When the EventBus fires an event, it will be handled by one of the handlers that is registered to listen to particular events.
As known jQuery lets us manage events on DOM level, however the EventBus lets us manage application-related-behavioral events.
A practical scenario where the EventBus proves to be useful is a well-known by now online-shop. When the user clicks the buy button, we might want that many operations be triggered behind the scenes, so that for eg. a shopping cart changes its list of items, a pop up is displayed, and the processed item disappears from the initial list of items. The implementation might imply registering three events for each of the required action. One or several listeners can be registered to listen for these events. When the buy button is issued, the EventBus fires these events, delegating the handle responsibility to registered listeners.
However, for the sake of simplicity, I prefer expressing a shorter example, and inspect that some events are indeed handled by one registered listener. The following snippet of code is straight-forward:
describe("EventBus", function () {
var openConnectionEvent, sendStreamEvent;
beforeEach(function () {
openConnectionEvent = "openConnectionEvent";
sendStreamEvent = "sendStreamEvent";
);
describe("having a collection of event/listeners - fixture", function () {
beforeEach(function () {
EventBus.registerEvent(sendStreamEvent, openConnectionListener);
EventBus.registerEvent(sendStreamEvent, sendStreamListener);
});
afterEach(function () {
EventBus.cleanBus();
});
describe("#fireEvents - operation", function () {
it("should trigger all registered listeners to handle the operation",
function () {
spyOn(console, "log").andCallThrough();
EventBus.fireEvent(sendStreamEvent);
expect(console.log).toHaveBeenCalled();
// ... other related expectations
});
});
});
}) ;
This specification pretty much shows what Jasmine is capable of. It provides the setup and the teardown mechanisms for "arranging" the context and some powerful spies as the Test Double Pattern implementation. Spies let us inspect what methods have been called and, optional, with what arguments. We can also instruct the calls to return specific results and we can also check if a method was invoked via the real implementation. This is the purpose of the andCallThrough() method.
Specifications integration in a Continuous Integration environment is a trivial task.
Following the "describe" blocks, we can easily understand what the behavior of the feature reflects.
BDD comes as an augmentation over the traditional TDD. BDD drives the application design to a more loosely coupled architecture. Refactoring phase introduces the incremental design.
Outside IN form of development is at the heart of BDD. It states that we should start from a high-level specification, and go down the road splitting it into more cohesive unities. This way we ensure that only what"s in the requirements get implemented and we avoid doing a premature BDUF. Outside In introduces a partial constraint, because we will not be able to run the high-level specification until all the inner-circle is fully implemented. Some frameworks provide a "cross-this-specification" mechanism (likewise Jasmine) that allows to bypass the high-level implementation until all the low-level specifications are done implemented. In the same time, it forces us to depend and think upon abstractions, which is a good approach in an OOD world. That way we postpone the real implementation until we have enough insight of the business problem.
The good part is that we can do BDD in a dynamic language like JavaScript as well, starting from the low-level specifications.
Probably if we do TDD well, we are already doing BDD.
http://www.clarityincode.com/software-maintenance
http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
http://ro.wikipedia.org/wiki/Divide_et_impera
https://github.com/cucumber/cucumber/wiki/Gherkin