Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Soem draft toughts on VCR

  • Loading branch information...
commit 79e78a800907adb5d7c0a44af6484f1ca5f03510 1 parent ee2d072
Craig R Webster authored April 10, 2012

Showing 1 changed file with 139 additions and 0 deletions. Show diff stats Hide diff stats

  1. 139  _drafts/2012-04-10-my-thoughts-on-vcr.md
139  _drafts/2012-04-10-my-thoughts-on-vcr.md
Source Rendered
... ...
@@ -0,0 +1,139 @@
  1
+---
  2
+title: My Thoughts On VCR
  3
+layout: post
  4
+author: Craig
  5
+---
  6
+
  7
+
  8
+Not even halfway through writing this and I'm realising that my dislike isn't
  9
+VCR. VCR is a good tool - one that I don't use, but still a good tool.
  10
+
  11
+My dislike is the way that too many people assue that VCR is the be-all and
  12
+end-all. VCR doesn't cover quite a few external API failure cases without your
  13
+help. You need to consider failure cases to make a good service. VCR doesn't
  14
+encourage (or discourage) good practice. You still need to think about the
  15
+structure of your code, remember the single responsibility principle,
  16
+refactor, etc.
  17
+
  18
+Pushing this post now as a draft because I'd like to finish it off to show how
  19
+my code might develop if I was using VCR and if I wasn't... but I'd also like
  20
+sleep :)
  21
+
  22
+
  23
+
  24
+
  25
+
  26
+
  27
+
  28
+
  29
+## What is VCR?
  30
+
  31
+  - Write normal code, don't always hit external API
  32
+  - Hooks into underlying HTTP implementation, caches responses
  33
+  - Cache key, expiry time, etc. are configurable
  34
+
  35
+## Why am I talking about it?
  36
+
  37
+  - Discussion with @t_crayford
  38
+  - Reminded me of frustrations on previous projects
  39
+  - Called it an abombination - not so cool of me (sorry @myronmarston, and thanks for contributing so much to the community), lots of hard work gone into the project, lots of people use it, it doesn't help me write cleaner code so I don't use it.
  40
+
  41
+## How have I seen it used?
  42
+
  43
+  - Payment processing - taking card payments - where external API charges for each request
  44
+
  45
+## What do I like about it?
  46
+
  47
+  - Makes easy work of testing external APIs
  48
+  - Captures fixture data for API calls
  49
+  - Don't need to worry about network access to test after capturing fixtures
  50
+  - Don't need to worry about external API being up to test
  51
+  - External API access won't slow test suite down as much: no network traffic
  52
+  - Tests look pretty much like the real code, there's little setup like I'd have to do with eg Fakeweb for a complex HTTP interaction
  53
+
  54
+## What do I dislike about it?
  55
+
  56
+  - Tests look pretty much like the real code, there's little setup like I'd have to do with eg Fakeweb for a complex HTTP interaction - and so there's less pain, easier to miss that cue to refactor.
  57
+  - Makes me less likely to think about failure cases - what happens when the API /is/ down or overloaded? Or just being too slow?
  58
+    + Can still be tested, but I'm less likely to consider failure cases when everything is fine all the time
  59
+  - Can hide API changes - what happens when the external API is updated or parts of it are deprecated?
  60
+  - Fixtures may be set to expire to help manage that - doing so makes having repeatable tests quite hard. Failure on CI for the past hour was because the fixtures expired there and hit the API when it wasn't responding correctly. Can't duplicate locally; my test run got the fixtures when the API was fine. Can I ignore the tests and deploy?
  61
+  - Expiring fixtures vs not being connected to network
  62
+  - Doesn't make me think about isolation of the test subject (which is fine if writing acceptance tests, may be okay for integration tests, and isn't cool for unit tests)
  63
+    + Again can still do this properly but less likely to consider issues when everything just works
  64
+  - Doesn't encourage separation of concerns
  65
+    + My MovieTicketPurchase class probably doesn't need to be concerned with HTTP, just with buying tickets for a movie
  66
+    + Less likely to feel testing pain so less likely to think about separating concerns when using VCR
  67
+    + I'm less likely to think about dependency injection or service locators eg before
  68
+
  69
+    class MovieTicketPurchase
  70
+      def initialize movie
  71
+        self.movie = movie
  72
+      end
  73
+
  74
+      # I'm coupled to the HTTP implementation.
  75
+      # I can't test without stubbing or mocking HTTP endpoints all over
  76
+      # the place.
  77
+      # If the ticket db changes my MovieTicketPurchase needs to change.
  78
+      # That feels wrong.
  79
+      def execute
  80
+        Net::HTTP.open 'ticketdb.com' do |http|
  81
+          response = http.post '/reservations', ...
  82
+          # TODO: handle all the error cases
  83
+          reservation = JSON.parse response.body
  84
+          http.put "/reservations/#{reservation["id"]}", :confirmed => true
  85
+        end
  86
+      end
  87
+    end
  88
+
  89
+    # Becomes this ->
  90
+
  91
+    class MovieTicketPurchase
  92
+      attr_accessor :ticket_database
  93
+      private :ticket_database=, :ticket_database
  94
+
  95
+      attr_accessor :movie
  96
+      private :movie=, :movie
  97
+
  98
+      # Something else tells me what I use to book tickets
  99
+      # As long as it responds to the same interface I don't care if it's
  100
+      # HTTP, FTP, email, or even some weird automated phone system.
  101
+      def initialize movie, ticket_database
  102
+        self.movie = movie
  103
+        self.ticket_database = ticket_database
  104
+      end
  105
+
  106
+      def execute
  107
+        reservation = ticket_database.reserve_ticket movie
  108
+        ticket_database.confirm reservation
  109
+        ...
  110
+      end
  111
+    end
  112
+
  113
+In the first example I need to use something like VCR - and something like
  114
+VCR makes the first one very easy to test.
  115
+
  116
+The second one is cleanly decoupled. I can test the second one without needing
  117
+to worry about HTTP at all, just by passing in a TicketDatabaseTestClient,
  118
+which feels nice to me. Of course, for at least one of the ticket_database
  119
+implementations I'll need to test an HTTP endpoint, but it feels more right
  120
+that the TicketDatabaseHTTPClient knows about HTTP than having the
  121
+MovieTicketPurchase know about HTTP.
  122
+
  123
+  class TicketDatabaseHTTPClient
  124
+    def reserve_ticket movie
  125
+      Net::HTTP.open 'ticketdb.com' do |http|
  126
+	response = http.post '/reservations', ...
  127
+	# TODO: handle all the error cases
  128
+	reservation = JSON.parse response.body
  129
+      end
  130
+    end
  131
+
  132
+    def confirm reservation
  133
+      Net::HTTP.open 'ticketdb.com' do |http|
  134
+        http.put "/reservations/#{reservation["id"]}", :confirmed => true
  135
+      end
  136
+    end
  137
+  end
  138
+
  139
+

0 notes on commit 79e78a8

Please sign in to comment.
Something went wrong with that request. Please try again.