the “terrain”

Terrain is a “pun” with lettuce and its “living place”, its about setup and teardown, and general hacking on your lettuce tests.

terrain.py

By convention lettuce tries do load a file called terrain.py located at the current directory.

Think at this file as a global setup place, there you can setup global hooks, and put things into lettuce “world”.

Note

You can also set a terrain.py file within the root of your Django project, when running the python manage.py harvest command, lettuce will load it. See more at the django command.

in practice

Try out this file layout:

/home/user/projects/some-project
       | features
            - the-name-of-my-test.feature
            - the-file-which-holds-step-definitions.py
            - terrain.py

Then add some setup at terrain.py and run lettuce

user@machine:~/projects/some-project$ lettuce

And notice terrain.py will be loaded before anything

world

For the sake of turning easier and funnier to write tests, lettuce “violates” some principles of good design in python, such as avoiding implicity and using global stuff.

The “world” concept of lettuce is mostly about “global stuff”.

in practice

Imagine a file located somewhere that will be imported by your application before lettuce start running tests:

from lettuce import world

world.some_variable = "yay!"

So that, within some step file you could use things previously set on world:

from lettuce import *

@step(r'exemplify "world" by seeing that some variable contains "(.*)"')
def exemplify_world(step, value):
    assert world.some_variable == value

And the feature could have something like:

Feature: test lettuce's world
  Scenario: check variable
    When I exemplify "world" by seeing that some variable contains "yay!"

world.absorb

It can be really useful to put functions and/or classes in lettuce.world

For example:

from lettuce import world

def my_project_wide_function():
    # do something

world.my_project_wide_function = my_project_wide_function

world.my_project_wide_function()

But as you can notice, as your project grows, there can be a lot of repetitive lines, not DRY at all :(

In order to avoid that, lettuce provides a “absorb” decorator that lives within “world”

Let’s see it in action:

from lettuce import world

@world.absorb
def my_project_wide_function():
    # do something

world.my_project_wide_function()

You can also use it with classes:

from lettuce import world

@world.absorb
class MyClass:
    pass

assert isinstance(world.MyClass(), MyClass)

And even with lambdas, but in this case you need to name it

from lettuce import world

world.absorb(lambda: "yeah", "optimist_function")

assert world.optimist_function() == 'yeah'

world.spew

Well, if you read the topic above, you may be guessing: “if I keep stashing things in lettuce.world, it may bloat it sometime, or confuse member names along my steps, or hooks.

For those cases after “absorbing” something, world can also “spew” it.

from lettuce import world

@world.absorb
def generic_function():
    # do something

assert hasattr(world, 'generic_function')

world.spew('generic_function')

assert not hasattr(world, 'generic_function')

hooks

Lettuce has hooks that are called sequentially before and after each action

Presented as python decorators, it can be used to take any actions you find useful.

For example, you can set a browser driver at world, and close the connection after all, populate database with test mass or anything you want, for example

Let’s see it from outside in

@before.all

This hook is ran before lettuce look for and load feature files

The decorated function takes NO parameters

from lettuce import *

@before.all
def say_hello():
    print "Hello there!"
    print "Lettuce will start to run tests right now..."

@after.all

This hook is ran after lettuce run all features, scenarios and steps

The decorated function takes a TotalResult as parameter, so that you can use the result statistics somehow

from lettuce import *

@after.all
def say_goodbye(total):
    print "Congratulations, %d of %d scenarios passed!" % (
        total.scenarios_ran,
        total.scenarios_passed
    )
    print "Goodbye!"

@before.each_feature

This hook is ran before lettuce run each feature

The decorated function takes a Feature as parameter, so that you can use it to fetch scenarios and steps inside.

from lettuce import *

@before.each_feature
def setup_some_feature(feature):
    print "Running the feature %r, at file %s" % (
        feature.name,
        feature.described_at.file
    )

@after.each_feature

This hooks behaves in the same way @before.each_feature does, except by the fact that its ran after lettuce run the feature.

from lettuce import *

@after.each_feature
def teardown_some_feature(feature):
    print "The feature %r just has just ran" % feature.name

@before.each_scenario

This hook is ran before lettuce run each scenario

The decorated function takes a Scenario as parameter, so that you can use it to fetch steps inside.

from lettuce import *
from fixtures import populate_test_database

@before.each_scenario
def setup_some_scenario(scenario):
    populate_test_database()

@after.each_scenario

This hooks behaves in the same way @before.each_scenario does, except by the fact that its ran after lettuce run the scenario.

from lettuce import *
from database import models
@after.each_scenario
def teardown_some_scenario(scenario):
    models.reset_all_data()

@before.each_outline

This hook is ran before lettuce run each scenario outline iteration

The decorated function takes a Scenario as parameter and outline dict where each column name is key

from lettuce import *

@before.each_outline
def setup_some_scenario(scenario, outline):
    print "We are now going to run scenario {0} with size={1}".format(scenario.name, outline["size"])

@after.each_outline

This hooks behaves in the same way @before.each_outline does, except by the fact that its ran after lettuce run the scenario outline iteration.

from lettuce import *

@after.each_outline
def complete_some_scenario(scenario, outline):
    print "We are now finished scenario {0} with size={1}".format(scenario.name, outline["size"])

@before.each_step

This hook is ran before lettuce run each step

The decorated function takes a Step as parameter, so that you can use it to fetch tables and so.

from lettuce import *

@before.each_step
def setup_some_step(step):
    print "running step %r, defined at %s" % (
        step.sentence,
        step.defined_at.file
    )

@after.each_step

This hooks behaves in the same way @before.each_step does, except by the fact that its ran after lettuce run the step.

from lettuce import *

@after.each_step
def teardown_some_step(step):
    if not step.hashes:
       print "no tables in the step"

django-specific hooks

Since lettuce officially supports Django, there are a few specific hooks that help on setting up your test suite on it.

@before.harvest

This hook is ran before lettuce start harvesting your Django tests. It can be very useful for setting up browser drivers (such as selenium), before all tests start to run on Django.

The decorated function takes a dict with the local variables within the harvest management command.

from lettuce import *
from lettuce.django import django_url
from selenium import selenium

@before.harvest
def prepare_browser_driver(variables):
    if variables.get('run_server', False) is True:
        world.browser = selenium('localhost', 4444, '*firefox', django_url('/'))
        world.browser.start()

@after.harvest

This hook is ran right after lettuce finish harvesting your Django tests. It can be very useful for shutting down previously started browser drivers (see the example above).

The decorated function takes a list of TotalResult objects.

from lettuce import *

@after.harvest
def shutdown_browser_driver(results):
    world.browser.stop()

@before.each_app

This hook is ran before lettuce run each Django app.

The decorated function takes the python module that corresponds to the current app.

from lettuce import *

@before.each_app
def populate_blog_database(app):
    if app.__name__ == 'blog':
        from blog.models import Post
        Post.objects.create(title='Nice example', body='I like writting!')

@after.each_app

This hook is ran after lettuce run each Django app.

The decorated function takes two arguments:

  • the python module that corresponds to the current app.
  • a TotalResult as parameter, so that you can use the result statistics somehow
from lettuce import *

@after.each_app
def clear_blog_database_if_successful(app, result):
    if app.__name__ == 'blog':
        if result.scenarios_ran is result.scenarios_passed:
            from blog.models import Post, Comment
            Comment.objects.all()
            Post.objects.all()

@before.runserver and @after.runserver

These hooks are ran right before, and after lettuce starts up the built-in http server.

The decorated function takes a lettuce.django.server.ThreadedServer object.

from lettuce import *
from django.core.servers.basehttp import WSGIServer

@before.runserver
def prepare_database(server):
    assert isinstance(server, WSGIServer)
    import mydatabase
    mydatabase.prepare()

@after.runserver
def say_goodbye(server):
    assert isinstance(server, WSGIServer)
    print "goodbye, see you soon"

@before.handle_request and @after.handle_request

These hooks are ran right before, and after lettuce’s built-in HTTP server responds to a request.

Both decorated functions takes these two arguments:

  • a django.core.servers.basehttp.WSGIServer object.
  • a lettuce.django.server.ThreadedServer object.
from lettuce import *
from django.core.servers.basehttp import WSGIServer

@before.handle_request
def print_request(httpd, server):
    socket_object, (client_address, size) = httpd.get_request()
    print socket_object.dup().recv(size)

@after.handle_request
def say_goodbye(httpd, server):
    socket_object, (client_address, size) = httpd.get_request()
    print "I've just finished to respond to the client %s" % client_address

Warning

all the handle_request hooks are run within a python thread. If something went wrong within a calback, lettuce can get stuck.