Tracking Function Calls in Python with Deride

When I was writing my Dropbox command-line client earlier this year, I found one aspect particularly frustrating: testing my interactions with the Dropbox client itself. This was frustrating because, after all, the Dropbox client is at the core of everything the CLI does. I ended up hand-crafting fake responses and exceptions and it worked well enough despite being a pain to assemble. However, I know that if and when the API changes I'll need to re-explore the API and update whatever's gone stale.

Around the same time, I started trying out OCaml for some personal projects. Unsurprisingly, I ended up reading some blog posts from Jane Street about their work with and on OCaml. When I read about their concept of "expect tests", I immediately thought of my frustration with crafting fake data for the Dropbox client. Specifically, it were these two sentences early in the post that spoke to me:

Expect tests allow you to write test scenarios without having to manually write out the output generated by the code you’re testing. Instead, that output is captured and recorded automatically for you, in a way that makes it easy to integrate into the source of the test.

Despite all the differences between the context of that post and the context of my cli, I was inspired by the idea that interactive exploration could help make testing easier.

deride

I put together a small Python module, deride, that might get me closer to automatically generating data for testing. The module currently exposes one class, Deride, that wraps a class or module and will record all the functions called, along with the arguments to and responses from those function calls. In practice, it's kind of similar to existing Python testing utilities like Mock in that you can still call any function you'd expect to find on the underlying object. It does this via a custom implementation of __getattr__, which is one part of how Python finds functions and attributes on objects. The class also squirrels away a few attributes, like the wrapped object and a list of function calls. The attribute lookup will return yet another wrapper, one that will add information about the function back to the original Deride instance. Once you've called some functions, it's time to use the other notable part of the module: get_calls. This returns a list of objects containing information about each function call.

The way I intend to use deride is in an interactive session where I wrap a module like requests or a class like the Dropbox client. After calling all the functions I might need to call in my real code, I can take the output of get_calls and turn that into a suitable facsimile of the third-party service I'm building on.

There are some rough edges to work on, like tracking exceptions thrown and attributes looked up, but hopefully this saves me a bit of time down the road in creating realistic test scenarios and easing the maintenance burden of code written against third-party services.