Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cutting off the Internet: Testing Applications That Use Requests

Cutting off the Internet: Testing Applications That Use Requests

requests is a very popular library for making HTTP requests in Python. Even though so many people use it, there's no thorough and exhaustive way to test applications that take advantage of it.

These slides are best read while listening to the presentation given at PyTennessee on 7 February 2015

Ian Cordasco

February 07, 2015
Tweet

More Decks by Ian Cordasco

Other Decks in Programming

Transcript

  1. Testing applications that use requests
    Cutting off the Internet

    View Slide

  2. View Slide

  3. Object Placeholder
    3
    www.rackspace.com

    View Slide

  4. 4
    • github3.py
    • twine
    • urllib3
    • sqlobject
    • betamax
    @sigmavirus24 (Ian the RFC Reader)
    www.rackspace.com
    • requests
    • Flake8
    • chardet
    • requests-toolbelt
    • uritemplate.py

    View Slide

  5. Object Placeholder
    5
    www.rackspace.com

    View Slide

  6. Why listen to me?
    6
    www.rackspace.com

    View Slide

  7. Why listen to me?
    7
    www.rackspace.com

    View Slide

  8. First, some review
    8
    www.rackspace.com

    View Slide

  9. What is a unit of code?
    9
    www.rackspace.com

    View Slide

  10. What is a unit test?
    10
    www.rackspace.com

    View Slide

  11. What are
    collaborators?
    11
    www.rackspace.com

    View Slide

  12. Why are they
    important?
    12
    www.rackspace.com

    View Slide

  13. class Foo(object):
    def fiz(self, *args):
    pass
    A Collaborators Example…
    13
    www.rackspace.com

    View Slide

  14. class Bar(object):
    def __init__(self):
    self.foo = Foo()
    def baz(self, *args):
    a = self.foo.fiz()
    # do stuff with a
    A Collaborators Example…
    14
    www.rackspace.com

    View Slide

  15. What are integration
    tests?
    15
    www.rackspace.com

    View Slide

  16. Reminder:
    Tests should be fast
    16
    www.rackspace.com

    View Slide

  17. Time for some code
    17
    www.rackspace.com

    View Slide

  18. def get_resource(session,
    resource,
    params=None,
    headers=None):
    What We’re Testing
    18
    www.rackspace.com

    View Slide

  19. url = url_from(resource)
    kw = {‘headers’: headers,
    ‘params’: params}
    resp = session.get(url, **kw)
    if resp.ok:
    return resp
    resp.raise_for_status()
    What We’re Testing
    19
    www.rackspace.com

    View Slide

  20. Why should we test
    this?
    20
    www.rackspace.com

    View Slide

  21. Why should we test
    this?
    21
    www.rackspace.com

    View Slide

  22. Why should we test
    this?
    22
    www.rackspace.com

    View Slide

  23. Approach the first
    23
    www.rackspace.com

    View Slide

  24. • responses
    • httpretty
    • requests-mock
    Available Libraries
    24
    www.rackspace.com

    View Slide

  25. • responses
    • httpretty
    • requests-mock
    Available Libraries
    25
    www.rackspace.com

    View Slide

  26. import my_module
    import responses
    import requests
    Our First Test
    26
    www.rackspace.com

    View Slide

  27. @responses.activate
    def test_get_resource():
    Our First Test
    27
    www.rackspace.com

    View Slide

  28. responses.add(
    responses.GET,
    ‘https://api.github.com/users’,
    body=('[{"id": 1, "url": “https://'
    'api.github.com/user/foo”}]'),
    status=200,
    content_type=‘application/json')
    Our First Test
    28
    www.rackspace.com

    View Slide

  29. session = requests.Session()
    resp = my_module.get_resource(
    session,
    ‘/users’)
    assert resp.json() is not None
    Our First Test
    29
    www.rackspace.com

    View Slide

  30. $ py.test responses_ex.py
    responses_ex.py .
    Running Our First Test
    30
    www.rackspace.com

    View Slide

  31. session = requests.Session()
    resp = my_module.get_resource(
    session, ‘/user’)
    assert resp.json() is not None
    Making Our Test Fail
    31
    www.rackspace.com

    View Slide

  32. $ py.test responses_ex.py
    responses_ex.py F
    Making Our Test Fail
    32
    www.rackspace.com

    View Slide

  33. $ py.test responses_ex.py
    responses_ex.py F
    ...
    ConnectionError: Connection
    refused: https://api.github.com/
    user
    Making Our Test Fail
    33
    www.rackspace.com

    View Slide

  34. responses.calls
    responses.calls[0].request
    Inspecting the Requests Made
    34
    www.rackspace.com

    View Slide

  35. Why is this good?
    35
    www.rackspace.com

    View Slide

  36. Why is this
    insufficient?
    36
    www.rackspace.com

    View Slide

  37. { "id": 3710711, "name": "github3.py", "full_name": "sigmavirus24/github3.py", "owner": { "login": "sigmavirus24", "id": 240830,
    "avatar_url": "https://avatars.githubusercontent.com/u/240830?v=3", "gravatar_id": "", "url": "https://api.github.com/users/sigmavirus24",
    "html_url": "https://github.com/sigmavirus24", "followers_url": "https://api.github.com/users/sigmavirus24/followers", "following_url":
    "https://api.github.com/users/sigmavirus24/following{/other_user}", "gists_url": "https://api.github.com/users/sigmavirus24/gists{/
    gist_id}", "starred_url": "https://api.github.com/users/sigmavirus24/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/
    users/sigmavirus24/subscriptions", "organizations_url": "https://api.github.com/users/sigmavirus24/orgs", "repos_url": "https://
    api.github.com/users/sigmavirus24/repos", "events_url": "https://api.github.com/users/sigmavirus24/events{/privacy}",
    "received_events_url": "https://api.github.com/users/sigmavirus24/received_events", "type": "User", "site_admin": false }, "private":
    false, "html_url": "https://github.com/sigmavirus24/github3.py", "description": "Python library for interfacing with the GitHub APIv3",
    "fork": false, "url": "https://api.github.com/repos/sigmavirus24/github3.py", "forks_url": "https://api.github.com/repos/sigmavirus24/
    github3.py/forks", "keys_url": "https://api.github.com/repos/sigmavirus24/github3.py/keys{/key_id}", "collaborators_url": "https://
    api.github.com/repos/sigmavirus24/github3.py/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/sigmavirus24/
    github3.py/teams", "hooks_url": "https://api.github.com/repos/sigmavirus24/github3.py/hooks", "issue_events_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/issues/events{/number}", "events_url": "https://api.github.com/repos/sigmavirus24/github3.py/events",
    "assignees_url": "https://api.github.com/repos/sigmavirus24/github3.py/assignees{/user}", "branches_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/branches{/branch}", "tags_url": "https://api.github.com/repos/sigmavirus24/github3.py/tags", "blobs_url": "https://
    api.github.com/repos/sigmavirus24/github3.py/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/sigmavirus24/github3.py/git/
    tags{/sha}", "git_refs_url": "https://api.github.com/repos/sigmavirus24/github3.py/git/refs{/sha}", "trees_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/sigmavirus24/github3.py/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/sigmavirus24/github3.py/languages", "stargazers_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/stargazers", "contributors_url": "https://api.github.com/repos/sigmavirus24/github3.py/contributors",
    "subscribers_url": "https://api.github.com/repos/sigmavirus24/github3.py/subscribers", "subscription_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/subscription", "commits_url": "https://api.github.com/repos/sigmavirus24/github3.py/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/sigmavirus24/github3.py/git/commits{/sha}", "comments_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/comments{/number}", "issue_comment_url": "https://api.github.com/repos/sigmavirus24/github3.py/issues/comments/
    {number}", "contents_url": "https://api.github.com/repos/sigmavirus24/github3.py/contents/{+path}", "compare_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/sigmavirus24/github3.py/merges",
    "archive_url": "https://api.github.com/repos/sigmavirus24/github3.py/{archive_format}{/ref}", "downloads_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/downloads", "issues_url": "https://api.github.com/repos/sigmavirus24/github3.py/issues{/number}",
    "pulls_url": "https://api.github.com/repos/sigmavirus24/github3.py/pulls{/number}", "milestones_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/milestones{/number}", "notifications_url": "https://api.github.com/repos/sigmavirus24/github3.py/notifications{?
    since,all,participating}", "labels_url": "https://api.github.com/repos/sigmavirus24/github3.py/labels{/name}", "releases_url": "https://
    api.github.com/repos/sigmavirus24/github3.py/releases{/id}", "created_at": "2012-03-13T19:58:53Z", "updated_at": "2015-01-15T08:07:34Z",
    "pushed_at": "2015-01-08T04:36:37Z", "git_url": "git://github.com/sigmavirus24/github3.py.git", "ssh_url": "[email protected]:sigmavirus24/
    github3.py.git", "clone_url": "https://github.com/sigmavirus24/github3.py.git", "svn_url": "https://github.com/sigmavirus24/github3.py",
    "homepage": "http://github3py.readthedocs.org/", "size": 8669, "stargazers_count": 268, "watchers_count": 268, "language": "Python",
    "has_issues": true, "has_downloads": true, "has_wiki": false, "has_pages": true, "forks_count": 91, "mirror_url": null,
    "open_issues_count": 22, "forks": 91, "open_issues": 22, "watchers": 268, "default_branch": "develop", "network_count": 91,
    "subscribers_count": 24 }
    Gigantic Resources
    37
    www.rackspace.com

    View Slide

  38. { "id": 3710711, "name": "github3.py", "full_name": "sigmavirus24/github3.py", "owner": { "login": "sigmavirus24", "id": 240830,
    "avatar_url": "https://avatars.githubusercontent.com/u/240830?v=3", "gravatar_id": "", "url": "https://api.github.com/users/sigmavirus24",
    "html_url": "https://github.com/sigmavirus24", "followers_url": "https://api.github.com/users/sigmavirus24/followers", "following_url":
    "https://api.github.com/users/sigmavirus24/following{/other_user}", "gists_url": "https://api.github.com/users/sigmavirus24/gists{/
    gist_id}", "starred_url": "https://api.github.com/users/sigmavirus24/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/
    users/sigmavirus24/subscriptions", "organizations_url": "https://api.github.com/users/sigmavirus24/orgs", "repos_url": "https://
    api.github.com/users/sigmavirus24/repos", "events_url": "https://api.github.com/users/sigmavirus24/events{/privacy}",
    "received_events_url": "https://api.github.com/users/sigmavirus24/received_events", "type": "User", "site_admin": false }, "private":
    false, "html_url": "https://github.com/sigmavirus24/github3.py", "description": "Python library for interfacing with the GitHub APIv3",
    "fork": false, "url": "https://api.github.com/repos/sigmavirus24/github3.py", "forks_url": "https://api.github.com/repos/sigmavirus24/
    github3.py/forks", "keys_url": "https://api.github.com/repos/sigmavirus24/github3.py/keys{/key_id}", "collaborators_url": "https://
    api.github.com/repos/sigmavirus24/github3.py/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/sigmavirus24/
    github3.py/teams", "hooks_url": "https://api.github.com/repos/sigmavirus24/github3.py/hooks", "issue_events_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/issues/events{/number}", "events_url": "https://api.github.com/repos/sigmavirus24/github3.py/events",
    "assignees_url": "https://api.github.com/repos/sigmavirus24/github3.py/assignees{/user}", "branches_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/branches{/branch}", "tags_url": "https://api.github.com/repos/sigmavirus24/github3.py/tags", "blobs_url": "https://
    api.github.com/repos/sigmavirus24/github3.py/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/sigmavirus24/github3.py/git/
    tags{/sha}", "git_refs_url": "https://api.github.com/repos/sigmavirus24/github3.py/git/refs{/sha}", "trees_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/sigmavirus24/github3.py/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/sigmavirus24/github3.py/languages", "stargazers_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/stargazers", "contributors_url": "https://api.github.com/repos/sigmavirus24/github3.py/contributors",
    "subscribers_url": "https://api.github.com/repos/sigmavirus24/github3.py/subscribers", "subscription_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/subscription", "commits_url": "https://api.github.com/repos/sigmavirus24/github3.py/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/sigmavirus24/github3.py/git/commits{/sha}", "comments_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/comments{/number}", "issue_comment_url": "https://api.github.com/repos/sigmavirus24/github3.py/issues/comments/
    {number}", "contents_url": "https://api.github.com/repos/sigmavirus24/github3.py/contents/{+path}", "compare_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/sigmavirus24/github3.py/merges",
    "archive_url": "https://api.github.com/repos/sigmavirus24/github3.py/{archive_format}{/ref}", "downloads_url": "https://api.github.com/
    repos/sigmavirus24/github3.py/downloads", "issues_url": "https://api.github.com/repos/sigmavirus24/github3.py/issues{/number}",
    "pulls_url": "https://api.github.com/repos/sigmavirus24/github3.py/pulls{/number}", "milestones_url": "https://api.github.com/repos/
    sigmavirus24/github3.py/milestones{/number}", "notifications_url": "https://api.github.com/repos/sigmavirus24/github3.py/notifications{?
    since,all,participating}", "labels_url": "https://api.github.com/repos/sigmavirus24/github3.py/labels{/name}", "releases_url": "https://
    api.github.com/repos/sigmavirus24/github3.py/releases{/id}", "created_at": "2012-03-13T19:58:53Z", "updated_at": "2015-01-15T08:07:34Z",
    "pushed_at": "2015-01-08T04:36:37Z", "git_url": "git://github.com/sigmavirus24/github3.py.git", "ssh_url": "[email protected]:sigmavirus24/
    github3.py.git", "clone_url": "https://github.com/sigmavirus24/github3.py.git", "svn_url": "https://github.com/sigmavirus24/github3.py",
    "homepage": "http://github3py.readthedocs.org/", "size": 8669, "stargazers_count": 268, "watchers_count": 268, "language": "Python",
    "has_issues": true, "has_downloads": true, "has_wiki": false, "has_pages": true, "forks_count": 91, "mirror_url": null,
    "open_issues_count": 22, "forks": 91, "open_issues": 22, "watchers": 268, "default_branch": "develop", "network_count": 91,
    "subscribers_count": 24 }
    Gigantic Resources
    38
    www.rackspace.com

    View Slide

  39. Using mock
    39
    www.rackspace.com

    View Slide

  40. mocked_session = mock.MagicMock()
    my_module.get_resource(mocked_session,
    ‘/users’)
    mocked_session.get.assert_called_once_with(
    ‘https://api.github.com/users’,
    params=None, headers=None
    )
    Mocking Birds
    40
    www.rackspace.com

    View Slide

  41. mocked_session = mock.MagicMock()
    my_module.get_resource(mocked_session,
    ‘/users’)
    mocked_session.get.assert_called_once_with(
    ‘https://api.github.com/users’,
    params=None, headers=None
    )
    Mocking Birds
    41
    www.rackspace.com

    View Slide

  42. mocked_session = mock.MagicMock()
    my_module.get_resource(mocked_session,
    ‘/users’)
    mocked_session.get.assert_called_once_with(
    ‘https://api.github.com/users’,
    params=None, headers=None
    )
    Mocking Birds
    42
    www.rackspace.com

    View Slide

  43. Approach the second
    43
    www.rackspace.com

    View Slide

  44. session = requests.Session()
    response = get_resource(session,
    ‘/users’)
    assert response.ok
    Live Integration
    44
    www.rackspace.com

    View Slide

  45. session = requests.Session()
    response = get_resource(session,
    ‘/users’)
    assert response.ok
    Live Integration
    45
    www.rackspace.com
    Now write and run 500 more of
    these

    View Slide

  46. session = requests.Session()
    response = get_resource(session,
    ‘/users’)
    assert response.ok
    Live Integration
    46
    www.rackspace.com
    Now write and run 500 more of
    these

    View Slide

  47. ......FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    Giant Bucket of FFFFFFFFFFFFFFFFFFFFFFFFFFFail
    47
    www.rackspace.com

    View Slide

  48. Approach the third
    48
    www.rackspace.com

    View Slide

  49. • vcr.py
    • betamax
    Available Libraries
    49
    www.rackspace.com

    View Slide

  50. • vcr.py
    • betamax
    Available Libraries
    50
    www.rackspace.com

    View Slide

  51. session = requests.Session()
    with vcr.use_cassette(‘vcrexample.yml’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0
    Example with vcr.py
    51
    www.rackspace.com

    View Slide

  52. session = requests.Session()
    with vcr.use_cassette(‘vcrexample.yml’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0
    Example with vcr.py
    52
    www.rackspace.com

    View Slide

  53. session = requests.Session()
    with vcr.use_cassette(‘vcrexample.yml’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0
    Example with vcr.py
    53
    www.rackspace.com

    View Slide

  54. session = requests.Session()
    recorder = betamax.Betamax(
    session, cassette_library_dir=‘.’)
    with recorder.use_cassette(‘betamaex’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0
    Example with betamax
    54
    www.rackspace.com

    View Slide

  55. Example with betamax
    55
    www.rackspace.com
    session = requests.Session()
    recorder = betamax.Betamax(
    session, cassette_library_dir=‘.’)
    with recorder.use_cassette(‘betamaex’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0

    View Slide

  56. Example with betamax
    56
    www.rackspace.com
    session = requests.Session()
    recorder = betamax.Betamax(
    session, cassette_library_dir=‘.’)
    with recorder.use_cassette(‘betamaex’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0

    View Slide

  57. Example with betamax
    57
    www.rackspace.com
    session = requests.Session()
    recorder = betamax.Betamax(
    session, cassette_library_dir=‘.’)
    with recorder.use_cassette(‘betamaex’):
    resp = my_module.get_resource(
    session, ‘/users’)
    assert response.ok
    users = resp.json()
    assert len(users) > 0

    View Slide

  58. Reality
    58
    www.rackspace.com

    View Slide

  59. Reality
    59
    www.rackspace.com

    View Slide

  60. Reality
    60
    www.rackspace.com

    View Slide

  61. THANK YOU
    RACKSPACE® | 1 FANATICAL PLACE, CITY OF WINDCREST | SAN ANTONIO, TX 78218
    US SALES: 1-800-961-2888 | US SUPPORT: 1-800-961-4454 | WWW.RACKSPACE.COM
    © RACKSPACE LTD. | RACKSPACE® AND FANATICAL SUPPORT® ARE SERVICE MARKS OF RACKSPACE US, INC. REGISTERED IN THE UNITED STATES AND OTHER COUNTRIES. | WWW.RACKSPACE.COM

    View Slide