31 May 2012

pyspecs - Minimalistic BDD in Python

pyspecs is a testing framework that strives to achieve more readable specifications (tests) by leveraging some fancy syntactic sugar and auto-discovery of tests/specifications (specs).

NOTE: This post applies to the 1.* branches of pyspecs. I plan on elaborating on the version 2.0 branch as part of a separate post.

pyspecs is a testing framework that strives to achieve more readable specifications (tests) by leveraging some fancy syntactic sugar and auto-discovery of tests/specifications (specs).

Installation is straightforward:

    $ pip install pyspecs

or…

    $ easy_install pyspecs

or…

    $ git clone https://mdwhatcott@github.com/mdwhatcott/pyspecs.git
    $ cd pyspecs
    $ python setup.py

Writing specs

Specifications are identified by subclassing from the spec class. From there the idea is then to lay out the specification in steps (given-when-then). The following steps are available to each subclass of spec as method decorators and are executed in the order listed:

given – The context for the specification, the initial setup phase.

when – This is where to invoke the action under test.

collect – Allows the aggregation of results for ease when making assertions.

then – This is where assertions are made (more details below) about the results arrived at in the when and collect steps.

after – Analogous to the tearDown method in unit-testing frameworks.

Assertions

The simplest assertion can be made by using the built-in assert statement:

assert 42 == 'The answer the life, the universe and everything'

For readability this project provides a more fluent method for making assertions:

# These imported names are all synonyms for the class that
# provides fluent assertions (Should). Use whichever provides
# the best readability.  The general patter is:
# >>> the([value]).should.[condition_method]([comparison_args])
#  or...
# >>> the([value]).should_NOT.[condition_method]([comparison_args]) # negated!

from pyspecs import the, this, that, it, then

this(42).should.equal(42) # this passes

this([1, 2, 3]).should.contain(2) # this also passes

the(list()).should.be_empty() # passes

it(1).should_NOT.be_greater_than(100) # passes

# raises AssertionError, caught by framework, logged as failure
that(200).should.be_less_than(0)

Example

from pyspecs import spec, given, when, then, the

class simple_addition(spec):
    @given
    def two_numbers(self):
        self.first = 2
        self.second = 3

    @when
    def we_add_them(self):
        self.result = add(self.first, self.second)

    @then
    def the_sum_should_equal_5(self):
        the(self.result).should.equal(5)

def add(a, b):
    return a + b

Execution of specs

Beyond providing the python library which will be explained below, installation provides two command-line scripts into the environment, meant to be invoked from the root of your project. Each will execute all specs in .py files ending in ‘spec.py’ or ‘specs.py’.

For one-time execution of specs:

    $ pyspecs

To begin an auto-test loop (runs all specs anytime a .py file is saved):

    $ pyspecs_idle

To increase verbosity (default is ‘dot’):

    $ pyspecs --verbosity=story

or…

    $ pyspecs_idle --verbosity=story

Output

$ pyspecs --verbosity=story

------------------------------------ Specs ------------------------------------

"simple addition"
     given two numbers
     when we add them
     then the sum should equal 5

---------------------------------- Statistics ----------------------------------

1 specs
1 assertions passed

Duration: 0.081s

(ok)

What now? Please try it out! If you like it but find yourself thinking “I wish that is supported ______” (fill in blank with feature you want) then by all means fork the github repository, hack away (adding tests as you go, if you please) and submit a pull request. Enjoy!