5

I want to understand how Behavior-Driven Development (BDD) can be applied to building CRUD applications.

I have read a lot on the subject but I just don't get how I'm supposed to apply it. I learn best by example, so I built a mini CRUD application and I would like for someone to complete it by adding the tests. The application is a simple registration form. The user fill the form with his email, then the server save it in the database.

Q1. What behavior am I supposed to test?

Q2. What part should I test?

Obviously, I want my user to be able to register their email. So is it that the behavior I'm supposed to test? Also, do I test the UI? Do I test that the email is really in the database after the registration? And, how do I test all that?

https://jsfiddle.net/cufxn40o/26/

Html

<h1>Registration Form</h1>
Email
<input type="text" id="email" />
<button type="button" onclick="client.register();">
  Register
</button>
<div id="error"></div>

Javascript

/**
 * The client represents the code that runs in the browser
 */
var client = {
    register: function () {
    var email = document.getElementById('email').value;
    ajaxPost(server.register, {email: email}, function(result){
        if(result.error){
        document.getElementById('error').innerHTML = result.error;
      } else {
        document.getElementById('error').innerHTML = '';
        alert('Registration successful!');
      }
    });
  }
};

/**
 * The server represents the code that runs on the server
 */
var server = {
    /**
   * The only method the client can call
   */ 
    register: function (model) {
    var error = server.validate(model);
    if(error) return {error: error};
    else
    {
        server.saveRegistration(model.email);
      return {success: true};
    }
  },

  validate:  function (model) {
    if(!model.email) return 'An email is required';
    if(!framework.isValidEmail(model.email)) return 'This is not a valid email';
    if(server.emailExists(model.email)) return 'This email already exist';
  },

  emailExists: function(email){
    return SqlDatabase.some(x => x === email);
  },

  saveRegistration: function(email){
    SqlDatabase.push(email);
  }
};

/**
 * This is a real SQL database!
 */ 
var SqlDatabase = [];


/**
 * THe framework used by the server code.
 * This code was not created by me
 */ 
var framework = {
    isValidEmail: function(email) {
    return email && email.indexOf('@') !== -1;
  }
};

/**
 * A utility function to simulate an ajax request in this example
 * You can ignore it
 */
function ajaxPost(url, data, callback){
    var result = url(data);
  callback(result);
}

4 Answers 4

9

I built a mini CRUD application and I would like for someone to complete it by adding the tests

If you are doing BDD you should have defined the required behaviour first. then written the tests, then built the app.

SO the behaviour would presumably be something like:

When a user is on the register page
and types their email in the email field
and presses the register button
then the email should be saved to the database

You can then automate each line, say with webdriver, put it all together into a test, running in a BDD framework (Cucumber?), which loads the page, inputs the email, clicks the button and checks the database for the entry. If it's present the test passes.

Now you can write your webpage. as you add functionality each line of the behaviour will turn green.

When the app is complete all your tests are green.

The supposed benefits of the approach are that..

  1. Because the Behaviour is written in 'plain english' the customer can write the tests.
  2. Because the tests are running against the actual app, UI instead of unit once they are green the app matches the spec, which was written by the customer, so it impossible to have bugs right!??
  3. Because each step of the test turns red or green you know how much more you have to do before you have completed all the time.

Obviously there are a few flaws in there. I would say the approach boils down to the 'plain english', multi-line test description of UI tests.

Which isn't a bad thing at all to have, but isn't drastically different from other approaches either.

7
  • Thank you for the answer but I find it a bit strange that you suggest to test the UI. Dan North himself advise to not tie your test to UI.dannorth.net/2011/01/31/whose-domain-is-it-anyway. Many other sources also state that building test on the UI produce brittle test that you will constantly need to redo. This is my experience as well using Selenium in the past.
    – Gudradain
    Aug 27, 2018 at 19:05
  • The creator of Cucumber also advice not to tie your test to the UI. joecolantonio.com/3-ways-ui-based-cucumber-bdd-can-go-bad
    – Gudradain
    Aug 27, 2018 at 19:08
  • 3
    UI tests are slow and brittle yes. But 'behaviour' is UI.
    – Ewan
    Aug 27, 2018 at 20:58
  • 3
    also, I would avoid getting sucked in to the question of whether BDD is good or bad and keep in mind that these guys have a vested interest in it being seen as 'good' and hyping their products
    – Ewan
    Aug 27, 2018 at 21:26
  • 2
    It is possible to test the functional behaviour of the system through the UI, this is not testing UI itself and it is an important distinction. I often combine cucumber with webDriver but I'm not testing the javascript UI itself. The intent of the tests should be to exercise the functional behaviour of the underlying system not the behaviour of the UI. Oct 9, 2018 at 16:26
7

Requirements

Typically you will define the behavior before you write the code. These start out as somewhat vague high-level requirements:

  • unregistered users should be able to register
  • registered users should be able to log in
  • registered users should be able to recover their password
  • registered user accounts should be locked after X failed attempts at logging in
  • unregistered users should not be able to use password recovery to steal someone else's password
  • when a registered uses log in, they should be granted access to objects owned by them

Behaviors

From these requirements we can define very specific behaviors. Each requirement will end up being implemented as one or multiple behaviors.

For example, if you're building a web app then the first bullet point may result in the following concrete behaviors:

  • when I am unregistered and visit the site, I should see a link that allows me to register
  • when I click the "register" link I should be taken to the registration form
  • when I fill in the form with valid information, it should create an account for me
  • when I am on the registration form, there should be a way to contact customer support for help

Testing scenarios

Those behaviors can then be refined into specific scenarios in your testing framework. For example:

Given I am an unregistered user
When I go to the home page
Then I am presented with a link to the registration page
So that I can become a registered user

The precise scenarios that you create may be different for different front-ends. For example, you might have different scenarios for visually impaired users, mobile users, desktop users, web users, api users, etc.

Summary

You start with vague requirements such as "unregistered user should be able to register", refine that to behaviors tied to a specific user ("when I go to the landing page I should see a link to register"), and from that you can generate specific test scenarios for different devices, etc. For one requirement you may have several behaviors, and for every behavior you may have several test scenarios.

6

I believe you have two separate questions here. One is: how can BDD be applied to a CRUD app, and the second is: how can I use BDD to build a simple registration app.

  1. BDD does not lend itself to certain contexts and one of them is building a pure CRUD app. By its nature CRUD has four well defined operations (for your given Customer data) and these are usually fairly trivial. There's no complex behaviour or special domain language. Therefore, if you want to be pedantic - use plain old TDD, or just use a simple CRUD library that you trust.

  2. A registration app, even a simple one, is not necessarily a CRUD app (and if you start off by thinking of it as a CRUD app you may be restricting your ability to abstract it more effectively). Most likely (depending on business) you will have a domain language (e.g. Customer, Member, Registration, Restricted. etc.), these domain concepts translate into your app code and could potentially have complex behaviour.

At this point it's important to remember that as well as being a methodology for driving development BDD is also a form of requirements analysis. By writing out the difference scenarios your user can go through, you will also develop (often together with a stakeholder of some sort) the language that describes the behaviour.

For example - your system users might be referred to as Members (e.g. a library member), the action they take might be called "register" by the business, or it might be called "sign-up" - its important that this language is agreed on. There are several ways in which you can initially explore the behaviour of your app (a.k.a "discovery") - the most effective strategy I've used is "Event Storming" followed by "Example Mapping". The former will give you a timeline of commands and events, the latter precise example of used.

In other words - don't start with Given/When/Then, instead draw out the timeline. Lets say you have a rule where members cannot borrow books until 24 hours after registration. Its a rule that can be clarified like this

A timeline:

  • Event: Jon Registered at 14:00 24/10/2018
  • Command: Jon try to borrow book "x" at 13:00 25/10/2018
  • (system logic: can book be loaned - no)
  • Outcome/Event: Jon told they cannot borrow yet

Alternate timeline:

  • Event: Jon Registered at 13:00 24/10/2018
  • Command: Jon borrow book "x" at 13:00 25/10/2018
  • (system logic: can book be loaned - yes)
  • Outcome/Event: Book "x" is loaned to Jon
  • Command: Jon return book "x" at 13:00 26/10/2018
  • (system logic: is book damaged? yes)
  • Outcome/Event: Book was "x" listed for repair 26/10/2018
  • Outcome/Command: Charge Jon £5

Now you can take slices of the timeline and write them up for automation with a tool that parses Given/When/Then format. Events become Givens, Command become Whens, and Outcome events become Thens. Lets take the first timeline and the second part of the alternate timeline.

Scenario: Not allowed to borrow a book - registration less than 24 hrs ago
    Given Jon Registered at 14:00 24/10/2018 
    When Jon tries to borrow book "x" at 13:00 25/10/2018   
    Then Jon should be told they cannot borrow any books yet

Scenario: allowed to borrow a book - registration more than 24 hrs ago
    Given Jon Registered at 13:00 24/10/2018 
    When Jon tries to borrow book "x" at 13:01 25/10/2018 
    Then book "x" should have been loaned to Jon at 13:01 25/10/2018 

Background:
    Given Jon Registered at 13:00 24/10/2018 

Scenario: Being charged for returning a book damaged and listing for repair
    Given Book "x" was loaned to Jon on 13:00 25/10/2018
    When Jon returns the book "x" at 13:00 26/10/2018   
    Then Book "x" should have been listed for repair at 13:05 26/10/2018
    And Jon should have been a £5 damage fee

From here you need to automate these "scenarios". The Given step will set up the system in a state where Jon has already been loaned the book (e.g. in the database?), The When step will execute the command (e.g. API? Web Form? you might need to automate a browser). The Then step will check that the book was listed for repair as a result (e.g. database again? or message queue?) and that the member was charged a fine. There might be feedback to the member you want to assert here as well which could also involved reading from the browser). Follow a TDD-like approach where this scenario will fail at first (since no business logic has been programmed).

1
  • 1
    This style of writing the BDD tests is actually the best. The steps are devoid to technical jargin and references to the user interface. This should be the answer. Oct 17, 2018 at 15:54
0

In addition to the valuable answers above, I'd add that when writing BDD tests for CURD - or actually for any kind of software - you should consider writing positive tests as well as negative tests.

This is an example of a positive test described by @Bryan OaKley :

Given I am an unregistered user
When I go to the home page
Then I am presented with a link to the registration page
So that I can become a registered user

And here are examples of negative tests:

Given I am registered but not an authenticated user
When I try to register myself with the same email I have registered with
I should get an error message that I am already registered

Given I am an authenticated user
When I go to the home page
I shouldn not be presented with a link to the registration page

Not the answer you're looking for? Browse other questions tagged or ask your own question.