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 full-size slide

  2. Object Placeholder
    3
    www.rackspace.com

    View full-size slide

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

    View full-size slide

  4. Object Placeholder
    5
    www.rackspace.com

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  18. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. Approach the first
    23
    www.rackspace.com

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  27. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  30. 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 full-size slide

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

    View full-size slide

  32. $ 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  36. { "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 full-size 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
    38
    www.rackspace.com

    View full-size slide

  38. Using mock
    39
    www.rackspace.com

    View full-size slide

  39. 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 full-size 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
    41
    www.rackspace.com

    View full-size 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
    42
    www.rackspace.com

    View full-size slide

  42. Approach the second
    43
    www.rackspace.com

    View full-size slide

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

    View full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

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

    View full-size slide

  47. Approach the third
    48
    www.rackspace.com

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  50. 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 full-size 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
    52
    www.rackspace.com

    View full-size 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
    53
    www.rackspace.com

    View full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. Reality
    58
    www.rackspace.com

    View full-size slide

  58. Reality
    59
    www.rackspace.com

    View full-size slide

  59. Reality
    60
    www.rackspace.com

    View full-size slide

  60. 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 full-size slide