DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Frameworks
  4. The Biggest Mistakes Django Developers Make When Using Lettuce

The Biggest Mistakes Django Developers Make When Using Lettuce

Gabriel Falcão user avatar by
Gabriel Falcão
·
Mar. 28, 12 · Interview
Like (0)
Save
Tweet
Share
9.07K Views

Join the DZone community and get the full member experience.

Join For Free

this post is the first in a series of posts about best practices when using lettuce , a testing framework for django.

when i first released lettuce , a framework for writting automated tests in django with user stories , i had no idea that it would have become so widely used. it’s been truly amazing to have seen it expand from brazil to the united states to china and many other countries. it’s even been translated into 15 languages.

however, over the last 6 months, i’ve observed a common usage that, for the reasons below, developers should avoid.

steps from step definition

like cucumber, lettuce supports calling other steps from a step definition. this can be a very handy functionality, but can easily become a source of code that is hard to maintain.

so why is this functionality available? although lettuce is a testing tool, step.behave_as was a patch ) that was incorporated in the codebase without complete test coverage. step.behave_as causes a step to call many others by parsing the text and calling them synchronously and sequentially .

some people like to use this functionality in order to make their scenario look leaner, which is fine. the actual problem is that this workflow is sub-optimal, so i would advise using this functionality with caution.

an example of step.behave_as usage (please avoid doing the same) as an example, let’s consider the following feature and its respective step definitions:

feature: exemplify why step.behave_as can be a not-so-good option

  scenario: login should work
    given i am at the login page
    when i type the username "root"
    and i type the password "123"
    and i try to perform the login
    then it works and i am redirected to the admin page

  scenario: get in the user management admin page
    given i log in as "admin" with password "123"
    when i click to manage users
    then i am pointed out to the user management page

defined as:

from lettuce import *
from lettuce.django import django_url
from splinter.browser import browser

pages = {
    "the login page": "/login",
    "the user management page": "/manage/users"
}

def page_name_is_valid(name):
    assert pages.has_key(name), \
        'the page "%s" is not mapped in the pages dictionary, ' \
        'check if you misspelled it or add into it' % name
    return true

@before.each_scenario
def prepare_browser(scenario):
    world.browser = browser()

@step(ur'i am at (.*)')
def i_am_at_some_url(step, name):
    assert page_name_is_valid(name)

    full_url = django_url(pages[name])
    world.browser.visit(full_url)

@step(ur'i type the (\s+) "(.*)"')
def i_type_some_value_into_a_field(step, field, value):
    browser.fill(field, value)

@step(ur'i try to perform the login')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#submit-login").click()

@step(ur'it works and i am redirected to (.*)')
def it_works_and_im_redirected_to(step, name):
    assert page_name_is_valid(name)

    current_url = world.browser.url
    full_url = django_url(pages[name])

    assert current_url == full_url, \
        'the current url is "%s" but should be "%s"' % (current_url, full_url)

@step(ur'i log in as "(\w+)" with password "(.*)"')
def i_log_in_as(step, username, password):
    step.behave_as(ur'''
        given i am at the login page
        when i type the username "%s"
        and i type the password "%s"
        and i try to perform the login
        then it works and i am redirected to the admin page
    ''' % (username, password))

@step(ur'i click to manage users')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#manage-users").click()

@step(ur'i am pointed out to (.*)')
def im_pointed_out_to(step, name):
    step.then("it works and i am redirected to %s" % name)

so… it looks kinda nice, why is it bad?

  1. step.behave_as implementation has issues .

  2. if you have to bypass parameters to the target steps, you will need to concatenate or interpolate strings, which will easily become a mess.

  3. if the string you pass as a parameter has typos, it’s a pain to debug.

  4. internally in lettuce’s codebase, every single step is built from an object which is bound to the parent scenario, and metadata such as where it is defined. the current step.behave_as implementation doesn’t remount those aspects properly, leading to craziness when debugging.

  5. once you hardcode strings in your step definitions, your test’s codebase will get hard to scale to more developers, and thus, hard to maintain.xxx


this is how lettuce works if you are not using step.behave_as:


please note the two aditional steps when you use it
:

the solution: refactor generic step definitions into @world.absorb methods

lettuce provides @world.absorb , a handy decorator, for storing useful and generic functions in a global test scope. the @world.absorb decorator literally absorbs the decorated function into the world helper and can be used right away in any other python files.

this decorator was created precisely for leveraging the refactoring of step definitions and terrain helpers by not requiring the developer to make too many imports from different paths, as well as to avoid making relative imports. let’s see how the first example would look like when using @world.absorb .

from lettuce import *
from lettuce.django import django_url
from splinter.browser import browser

pages = {
    "the login page": "/login",
    "the user management page": "/manage/users"
}

def page_name_is_valid(name):
    assert pages.has_key(name), \
        'the page "%s" is not mapped in the pages dictionary, ' \
        'check if you misspelled it or add into it' % name
    return true

@world.absorb
def go_to_page(name):
    assert page_name_is_valid(name)

    full_url = django_url(pages[name])
    world.browser.visit(full_url)

@world.absorb
def assert_current_page_is(name):
    assert page_name_is_valid(name)

    current_url = world.browser.url
    full_url = django_url(pages[name])

    assert current_url == full_url, \
        'the current url is "%s" but should be "%s"' % (current_url, full_url)

@before.each_scenario
def prepare_browser(scenario):
    world.browser = browser()

@step(ur'i am at (.*)')
def i_am_at_some_url(step, name):
    world.go_to_page(name)

@step(ur'i type the (\s+) "(.*)"')
def i_type_some_value_into_a_field(step, field, value):
    browser.fill(field, value)

@step(ur'i try to perform the login')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#submit-login").click()

@step(ur'it works and i am redirected to (.*)')
def it_works_and_im_redirected_to(step, the_expected_page):
    world.assert_current_page_is(the_expected_page)

@step(ur'i log in as "(\w+)" with password "(.*)"')
def i_log_in_as(step, username, password):
    world.go_to_page("the login page")
    browser.fill("username", username)
    browser.fill("password", password)
    browser.find_by_css("button#submit-login").click()
    world.assert_current_page_is("the admin page")

@step(ur'i click to manage users')
def try_to_perform_login(step, field, value):
    browser.find_by_css("button#manage-users").click()

@step(ur'i am pointed out to (.*)')
def im_pointed_out_to(step, expected_page):
    world.assert_current_page_is(the_expected_page)

the step definition def i_log_in_as now calls helpers that are available in the world helper.

conclusion

you can easily notice that in the example above, **@world.absorb** allows for better maintainability and cleaner step definitions.

  1. hardcoded strings would require manual updates when any related step-definitions has its regex changed.

  2. step definitions that are multiple-lines long now just bypass the parameters into single-line function calls.

  3. when the hardcoded string has typos, no syntax error will occur yet the test will fail with a misleading error message.

dev Django (web framework)

Published at DZone with permission of Gabriel Falcão. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Custom Validators in Quarkus
  • Spring Boot vs Eclipse MicroProfile: Resident Set Size (RSS) and Time to First Request (TFR) Comparative
  • What Are the Benefits of Java Module With Example
  • Multi-Cloud Integration

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: