more than a side salad: behaviour driven testing and test driven design in django with lettuce
DESCRIPTION
TRANSCRIPT
more than aside salad:behaviour driven testing andtest driven design in Djangowith Lettuce
Danielle Madeley blogs.gnome.org/danni dannipenguin
Just what is testdrivendevelopment?
Tests first;code second
http://www.nilkanth.com/2007/06/08/threemonkeysoftestdrivendevelopment/
But how do wewrite tests whenwe don't knowwhat the codelooks like?
Don't test code;test behaviours
As a visitor to the siteI want to create an account using my GoogleloginSo that I can log in without needing anotherpassword
GivenI have a valid Google account
WhenI visit the siteAnd I click create account
ThenI am redirected to Google
http://www.flickr.com/photos/27369469@N08/2660160225
Lettucehttp://lettuce.it
http://www.flickr.com/photos/65567316@N00/2742564457
Feature: Authenticate to API
As a searcher/API user I want to authenticate to the API So that I can make queries
Scenario: I am not authenticated to the API When I get the resource "/api/v3/hello/"
Then I get the response code 401
Steps
http://www.flickr.com/photos/60364452@N00/504844510
from lettuce import step, world
@step(r'I get the resource "([̂"]*)"')def get_resource(step, url): """ Make a GET request to the given URL """
world.response = world.client.get(url)
Steps arestateful
http://www.flickr.com/photos/chrisconnell/3201514924/
from django.test.client import Clientfrom lettuce import before, step, worldfrom nose.tools import assert_equals
@before.each_scenariodef set_default_client(scenario):
world.client = Client()
@step(r'I get the resource "([̂"]*)"')def get_resource(step, url):
world.response = world.client.get(url)
@step(r'I get the response code (\d+)')def check_response_code(step, code):
code = int(code) assert_equals(world.response.status_code, code)
./manage.pyharvest
# settings.py
INSTALLED_APPS = ( ... 'lettuce.django', 'myapp',)
LETTUCE_APPS = ( 'myapp',)
LETTUCE_USE_TEST_DATABASE = True
Selenium
http://www.flickr.com/photos/21663307@N02/3599012763
from lettuce import before, world# import external lettuce stepsimport lettuce_webdriver.webdriver
@before.alldef set_browser(): """ Create a browser instance for use in tests. """
world.browser = webdriver.PhantomJS(...) world.browser.set_window_size(1200, 800)
def site_url(url):
base_url = 'http://%s' % socket.gethostname()
if server.port is not 80: base_url += ':%d' % server.port
return urlparse.urljoin(base_url, url)
@step("I visit the site")def open_site(step):
step.given('I visit "%s"' % site_url('/'))
Scenario: Add consumer with blank Contact name When I log in to admin with username "admin" and password "secret" And I click "Consumers" And I click "Add consumer" And I fill in "Organisation" with "OOOO" And I fill in "Contact name" with "" And I fill in "Contact number" with "0399999999" And I fill in "Website" with "www.test.com" And I press "Save"
Then I should see "This field is required" And there should be 0 consumers in the database
Fixtures
Feature: Create new API key
As an administrator I want to create a new API key So that I can enable others to query API
Background: Given I have users in the database: | username | password | is_superuser | | admin | secret | true |
Built in steps usingDjango's modelintrospectionGiven I have users in the database: ...
Then there should be 1 consumer in the database
And consumer should be present in the database: ...
Which are extendable@creates_models(User)def create_user(step): data = hashes_data(step) for hash_ in data: is_superuser = hash_.pop('is_superuser', False
if is_superuser: user = User.objects.create_superuser(**hash_) else: user = User.objects.create_user(**hash_)
user.save()
reset_sequence(User)
Best practice
Given I have items in my cart
When I go to checkoutAnd I pay for the items
Then everything worked
write reusablesteps
write reusable scenariosScenario Outline: I edit a user to add a team Given users have permissions: | user | organisation | permission | | [email protected] | OrgCorp | organisation_user | And I log in with email "<email>" through the profile server
When I visit site page "organisation/1/admin/user/4/edit" And I select option "Team 1" from selector "Team" And I press "Save"
Then there should be 1 organisation member metadata in the database Then organisation member metadata should be present in the database: | organisation__name | user__email | team | | OrgCorp | [email protected] | Team 1 |
Examples: | email | | [email protected] | | [email protected] | | [email protected] |
tie each feature fileto a single story
include scenariosand anti-scenarios
Other uses
http://www.flickr.com/photos/omcoc/3050378171/
fin ;-Pquestions?
colophon This presentation was done in reveal.js using Junction, Cantarelland Source Code Pro with photography from the Creative Commons.
Infoxchange is hiring. Come and chat to me!
blogs.gnome.org/danni dannipenguin
http://www.flickr.com/photos/mau3ry/3763640652/