Building Nimoy: Driving The Behaviour

Noam Tenne
Python Pandemonium
Published in
2 min readDec 31, 2017

--

Documenting the process of building Nimoy — Part 4.

In our last instalment we reviewed how Nimoy identifies which modules and classes are executable Specifications. Now let’s talk BDD!

Behave and py.test BDD have you write .feature files that describe scenarios. Test cases back the Feature files and every scenario step is a Python method. This system is clunky and hard to maintain. But with Nimoy, BDD units are now single methods rather than a collection of methods. AND THERE’S NO NEED FOR FEATURE FILES!

A simple Nimoy feature method looks like:

Each step is a with(Context Manager) block for both ascetic and functional reasons.
Ascetic — Separate steps are clear and labeled with a meaningful keyword, enhancing readability.
Functional — A managed context of each block allows us to control the side effects of every step.

But with expects a context manager object, and setup, when and then are not valid Python keywords. How is this even working?

AST transformations of course! Using transformations, we’ll swap the step name with a context manager object.

First, let’s create a simple context class:

The class respects the context manager API by implementing __enter__ and __exit__. It also accepts one single parameter — block_type. Preserving the type will be useful once we begin managing complex contexts.

We want a “single point of truth” to create new instance of the context. The most natural place is the Specification class. This makes sense because all Nimoy specifications subclass Specification. So let’s expose a method to create the instance:

_feature_block_context begins with an underscore to signify that it’s an internal method.

Now all that’s left for us is to transform
with setup:
to
with self._feature_block_context('setup'):

As in previous times, we’ll use an AST node transformer class:

We first read the expression given to the with expression to resolve the type of block. We then replace the existing expression with a method call. The target of the method call is self. The method we call is _feature_block_context. Regarding parameters, we’re only interested in passing block_type.

Now each block is valid, executable Python code and we get to keep the metadata! Hooray!

--

--