Tag Archives: BDD

Testing Django – part 2 – lettuce

The first part of this series was an introduction to Django testing with nose. In this part we will shed light on lettuce to practice Behaviour Driven Development (BDD). You can find all the other parts here:

The frame work lettuce is heavily inspired by cucumber – a BDD framework for Ruby. The key idea is to make specifications a communication tool between the different stake holders of a project (e.g. owner and the developer) and testable as well. To accomplish that the feature descriptions are written in a business-readable domain-specific language. A second layer (the so called step definition) is used to translate these specifications into testable code.

Starting with the dummy project from the first part we first need to install lettuce:

$ sudo pip install lettuce

And we have to configure the app to make use of lettuce (in the settings.py file):

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'fruitsalad.fruits', # From the first part
    'django_nose', # From the first part
    'lettuce.django' # Add now
)

By doing so manage.py gets a new command called harvest. Running this command will give the following output:

$ ./manage.py harvest
Django's builtin server is running at 0.0.0.0:8001
Oops!
could not find features at ./fruits/features

0 feature (0 passed)
0 scenario (0 passed)
0 step (0 passed)

This means we have to add features. So we setup a dedicated folder

$ mkdir fruits/features

and create a file called fruits/features/fruits_basics.feature. The precise name is not important as long as it ends with .feature. Let’s add the a feature definition to this file:

Feature: Fruits should be yummy.

    Scenario: Having a cherry
        Given I access the url "/fruits/cherry"
        Then I see the content of the cherry page

Lettuce digests this feature and looks for the step definition of it. As there is none so far it offers us some snippets that we can use:

$ ./manage.py harvest
Django's builtin server is running at 0.0.0.0:8000

Feature: Fruits should be yummy.              # fruits/features/fruits_basics.feature:1

  Scenario: Having a cherry                   # fruits/features/fruits_basics.feature:3
    Given I access the url "/fruits/cherry"   # fruits/features/fruits_basics.feature:4
    Then I see the content of the cherry page # fruits/features/fruits_basics.feature:5

1 feature (0 passed)
1 scenario (0 passed)
2 steps (2 undefined, 0 passed)

You can implement step definitions for undefined steps with these snippets:

# -*- coding: utf-8 -*-
from lettuce import step

@step(u'Given I access the url "(.*)"')
def given_i_access_the_url_group1(step, group1):
    pass
@step(u'Then I see the content of the cherry page')
def then_i_see_the_content_of_the_cherry_page(step):
    pass

0 feature (0 passed)
0 scenario (0 passed)
0 step (0 passed)
----------------------------------------

We copy the given code into a the step file fruits/features/fruits_basics.py and run lettuce again:

$ ./manage.py harvest
Django's builtin server is running at 0.0.0.0:8000

Feature: Fruits should be yummy.              # fruits/features/fruits_basics.feature:1

  Scenario: Having a cherry                   # fruits/features/fruits_basics.feature:3
    Given I access the url "/fruits/cherry"   # fruits/features/fruits_basics.py:5
    Then I see the content of the cherry page # fruits/features/fruits_basics.py:9

1 feature (1 passed)
1 scenario (1 passed)
2 steps (2 passed)
----------------------------------------

The test passes now but only as we did not assert anything so far. Let change this. We use Django‘s testing client which we covered shortly in the first part to test the existence of a page:

                                                            
from lettuce import step
from lettuce.django import django_url
from lettuce import world
from django.test.client import Client

@step(u'Given I access the url "(.*)"')
def given_i_access_the_url_group1(step, url):
    world.client = Client()
    world.response = world.client.get(django_url(url))

@step(u'Then I see the content of the cherry page')
def then_i_see_the_content_of_the_cherry_page(step):
    assert world.response.content == "Show the cherry page"

world is a little helper construction that is explained here. Let’s run lettuce again with these real specs:

/manage.py harvest
Django's builtin server is running at 0.0.0.0:8000

Feature: Fruits should be yummy.              # fruits/features/fruits_basics.feature:1

  Scenario: Having a cherry                   # fruits/features/fruits_basics.feature:3
    Given I access the url "/fruits/cherry"   # fruits/features/fruits_basics.py:8
    Then I see the content of the cherry page # fruits/features/fruits_basics.py:13

1 feature (1 passed)
1 scenario (1 passed)
2 steps (2 passed)

The test passes and we are happy. We only applied Python‘s assert function in these examples but as described here nose’s assertion (e.g. assert_equal, assert_true) functions that we met in the first part of the series can be used here, too.