Pro Yearly is on sale from $80 to $50! »

Because Web API Testing Should Be Easy

Because Web API Testing Should Be Easy

Web APIs are often called REST APIs, which recently became a part of the everyday life of many Python developers. Sure, it can be a blast to build an API with frameworks like Django or Flask, but writing tests for it can be a tedious drag: asserting every single HTTP code, set of headers, JSON responses, error states… you know the drill. Despite all that you do, you still need to assure your API clients won’t be exposed to any unexpected surprises. At Apiary, we've developed an Open Source testing framework called Dredd, which has baked in first-class Python support. It does all the heavy lifting and boring stuff for you while allowing you to alter the test cases with arbitrary Python code. Let Judge Dredd do your API justice.

7b2e4bf7ecca28e530e1c421f0676c0b?s=128

Honza Javorek

March 13, 2017
Tweet

Transcript

  1. Because Web API Testing Should Be Easy Honza Javorek

  2. Because Web API Testing Should Be Easy Honza Javorek

  3. Design APIs for humans and test what you promised Honza

    Javorek
  4. Honza honzajavorek.cz

  5. None
  6. Interface of libraries pip install django

  7. Interface of systems curl https://api.github.com/repos/django/django curl http://www.cnb.cz/cs/financni_trhy/devizovy_trh/ kurzy_devizoveho_trhu/denni_kurz.txt

  8. User interface

  9. import urllib2 gh_url = 'https://api.github.com' req = urllib2.Request(gh_url) password_manager =

    urllib2.HTTPPasswordMgrWithDefaultRealm() password_manager.add_password(None, gh_url, 'user', 'pass') auth_manager = urllib2.HTTPBasicAuthHandler(password_manager) opener = urllib2.build_opener(auth_manager) urllib2.install_opener(opener) handler = urllib2.urlopen(req) print handler.getcode() print handler.headers.getheader('content-type') Do you like this interface?
  10. Requests: HTTP for Humans

  11. >>> r = requests.get('https://api.github.com/user', ... auth=('user', 'pass')) >>> r.status_code 200

    >>> r.headers['content-type'] 'application/json; charset=utf8' >>> r.encoding 'utf-8' >>> r.text '{"type":"User"...' >>> r.json() {'disk_usage': 368627, 'private_gists': 484, ...} Do you like this interface?
  12. None
  13. How did Kenneth do it?

  14. How do you design the interface?

  15. def test_basic_building(): req = requests.Request() req.url = 'http://kennethreitz.org/' req.data =

    {'life': '42'} pr = req.prepare() assert pr.url == req.url assert pr.body == 'life=42'
  16. Writing tests first helps to design the interface TDD

  17. test > RED > implement > test > GREEN req

    = requests.Request() req.url = 'http://kennethreitz.org/' req.data = {'life': '42'} pr = req.prepare() assert pr.url == req.url assert pr.body == 'life=42'
  18. Writing down behavior first helps to design the interface BDD

  19. Feature: Status code Background: Given you expect HTTP status code

    "200" Scenario: Different real response status When real status code is "500" Then Gavel will set some error for "status code" And Request or Response is NOT valid Scenario: Response status code match When real status code is "200" Then Gavel will NOT set any errors for "status code" And Request or Response is valid Gherkin / Cucumber
  20. Testable documentation!

  21. RDD

  22. RDD Readme Driven Development http://tom.preston-werner.com/2010/08/23/readme-driven- development.html

  23. # Requests The `requests` library allows you to perform HTTP

    requests from your Python code. ## Example ```python >>> r = requests.get('https://github.com') >>> r.status_code 200 ``` ## License MIT README.md
  24. Readme Driven Development • chance to think through the project

    first • docs are ready - no need to write them retroactively • your team can use the interface before it exists • easy to discuss the interface with everyone
  25. Interface in README = Essential interface user expects

  26. README must not get out of sync with code

  27. How do we ensure implementation matches the design?

  28. python -m doctest README.md doctest

  29. language: "python" python: - "3.6" script: - "python -m doctest

    README.md" Continuous Integration
  30. What if we could design and test web APIs like

    this?
  31. # Calendar API The API gives you various means to

    work with date and time. ## GET /now Provides you with current date and time. - Response 200 (application/json) ```json { "day": 29, "month": 2, "year": 2017, "hour": 11, "minute": 45, "second": 38 } ``` API.md
  32. None
  33. dredd API.md http://localhost:8000 Dredd

  34. None
  35. None
  36. language: "python" python: - "3.6" before_install: - "npm install -g

    dredd" script: - "dredd API.md http://localhost:8000" Continuous Integration
  37. None
  38. None
  39. Testing implementation against design allows you designing before implementing

  40. Designing before implementing allows you better design

  41. Dredd allows you better design

  42. Demo

  43. Remember • think first, design first, docs first, test first

    • discuss the the interface design before implementing • use the interface before implementing (mocks, tests) • have your interface design as a single source of truth • test implementation against the design
  44. apiary.io