More than a side salad: behaviour driven testing and test driven design in Django with Lettuce

4,399 views

Published on

Published in: Technology, Business
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,399
On SlideShare
0
From Embeds
0
Number of Embeds
1,743
Actions
Shares
0
Downloads
8
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

More than a side salad: behaviour driven testing and test driven design in Django with Lettuce

  1. 1. more than a side salad: behaviour driven testing and test driven design in Django with Lettuce Danielle Madeley  blogs.gnome.org/danni  dannipenguin
  2. 2. Just what is test driven development?
  3. 3. Tests first; code second
  4. 4. http://www.nilkanth.com/2007/06/08/three­monkeys­of­test­driven­development/
  5. 5. But how do we write tests when we don't know what the code looks like?
  6. 6. Don't test code; test behaviours
  7. 7. As a visitor to the site I want to create an account using my Google login So that I can log in without needing another password
  8. 8. 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
  9. 9. Lettuce http://lettuce.it http://www.flickr.com/photos/65567316@N00/2742564457
  10. 10. 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
  11. 11. Steps http://www.flickr.com/photos/60364452@N00/504844510
  12. 12. 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)
  13. 13. Steps are stateful http://www.flickr.com/photos/chrisconnell/3201514924/
  14. 14. from django.test.client import Client from lettuce import before, step, world from nose.tools import assert_equals @before.each_scenario def 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)
  15. 15. ./manage.py harvest
  16. 16. # settings.py INSTALLED_APPS = ( ... 'lettuce.django', 'myapp', ) LETTUCE_APPS = ( 'myapp', ) LETTUCE_USE_TEST_DATABASE = True
  17. 17. Selenium http://www.flickr.com/photos/21663307@N02/3599012763
  18. 18. from lettuce import before, world # import external lettuce steps import lettuce_webdriver.webdriver @before.all def set_browser(): """ Create a browser instance for use in tests. """ world.browser = webdriver.PhantomJS(...) world.browser.set_window_size(1200, 800)
  19. 19. 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('/'))
  20. 20. Scenario: Add consumer with blank Contact name When I log in to admin with username "admin" and 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
  21. 21. Fixtures
  22. 22. 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 |
  23. 23. Built in steps using Django's model introspection Given I have users in the database: ... Then there should be 1 consumer in the database And consumer should be present in the database: ...
  24. 24. Which are extendable @creates_models(User) def create_user(step): data = hashes_data(step) for hash_ in data: is_superuser = hash_.pop('is_superuser', Fals if is_superuser: user = User.objects.create_superuser(**ha else: user = User.objects.create_user(**hash_) user.save() reset_sequence(User)
  25. 25. Best practice
  26. 26. Given I have items in my cart When I go to checkout And I pay for the items Then everything worked
  27. 27. write reusable steps
  28. 28. write reusable scenarios Scenario Outline: I edit a user to add a team Given users have permissions: | user | organisation | permission | | peon@org.org | OrgCorp | organisation_user | And I log in with email "<email>" through the profile serve 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 Then organisation member metadata should be present in the | organisation__name | user__email | team | | OrgCorp | peon@org.org | Team 1 | Examples: | email | | god@god.org | | superuser@org.org | | poweruser@org.org |
  29. 29. tie each feature file to a single story
  30. 30. include scenarios and anti-scenarios
  31. 31. Other uses http://www.flickr.com/photos/omcoc/3050378171/
  32. 32. fin ;-P questions? colophon This presentation was done in reveal.js using Junction, Cantarell and 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/

×