Skip to content

Instantly share code, notes, and snippets.

@elliot42
Created July 29, 2014 21:00
Show Gist options
  • Save elliot42/40f1f7546b0f1a67f70f to your computer and use it in GitHub Desktop.
Save elliot42/40f1f7546b0f1a67f70f to your computer and use it in GitHub Desktop.

This is pretty much testing "does events-between still do the right thing?". But we already have a whole bunch of tests for events-between.

I would be comfortable not testing this stuff, because the delegation from one level to the other is so direct.

Alternatively, I've been slowly thinking about the following strategy for the past couple of years:

  1. When you have a complex underyling layer that's already tested, layers on top of it / delegating to it naturally are sort of a superset of the underlying functionality. For example, a valid HTTP request must both be valid at the TCP level AND enforce application-layer semantics at the HTTP layer.
  2. The goal is not have to re-test the underlying layer at every successful layer above it, especially when a ton of hard work and complexity went into the underlying layer, with the goal being to make the upper layers simpler and independent of the underlying layer.

How do you achieve testing "did my Slice do the underlying events-between properly?"

In the Ruby/Python/Rails/Django/BDD world, what people do is mocking and stubbing--digging out the guts of the objects/functions and replacing them with special ones saying "did you just delegate to the underlying layer properly?" This leads to some pretty tight coupling to the implementation, and basically a lot of weird voodoo that is super easy to break. We've seen this with midje.

What is the alternative?

With testing the question is always "what did I just check and how confident am I that what I checked represents the right scenario to check for?" When you have a delegation problem like this, you can decompose it into:

  1. Does the underlying delegatee do the right thing when passed the right arguments? (Tested in its own tests)
  2. Did I pass it the right arguments?

Ordinarily, inside the body of a normal function, you can't tell what arguments got passed around in there. BUT if you decompose the argument generation you can tell if you'll be passing the right arguments.

  1. Does the underlying delegatee do the right thing when passed the right arguments? (Tested in its own tests)
  2. Does my argument generator take the right input arguments and return the right output arguments (This is simple and purely functional to test! In this case it would check whether the return value has successfully translated clj-time dates to unix timestamps).
  3. Did I pass the arguments correctly from the argument generator output to the underlying delegatee? (This is so trivial it can be untested (basically only vulnerable to completely obvious typos), or you can try to avoid the problem if you can get a situation like (-> foo bar) where literally you cannot mess up passing the arguments correctly from the output of one function to the input of the other function.)

So in our example here:

  • delegatee: eb/events-between
(defn arg-generator [{:keys [company-id begin end] : as slice} config] ;; NOT A GOOD FUNCTION NAME
  [company-id config (instant<-epoch begin) (instant<-epoch end)])

;; the function above is trivially testable

 ;; not endorsing the existence of this function, but here's how you'd do it compositionally
(defn fetch-slice [
  (apply eb/events-between (arg-generator this config)))

In the above system, because it's split out argument generation from the actual invocation, testing the argument generation (which is where things will go wrong) is trivial, and you probably don't need to test fetch-slice itself because most of the functional in there is handled by clojure/core--the only thing that could go wrong is if the definition totally flubs the call to arg-generator, this or config. You could go even farther to see if you could make:

(def fetch-slice (comp eb/events-between arg-generator))

But that would require a modification to events-between

Anyway I hope you get the idea. I am of course super excited about this technique and think that it could help in situations like there where there is a high cost/inconvenience to needing to set up and re-test underlying layers. I hope you'll consider using a technique like this or find a different kind of related one, and let me know what you think. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment