Quickstart

Installation

>>> pip install pspecs

Basic structure

# my_first_spec.py
from pspecs import Context, let

class DescribeMath(Context):

    @let
    def numbers(self):
        return [1, 2, 3]

    class DescribeSum(Context):

        @let
        def sum(self):
            return sum(self.numbers)

        def it_should_be_six(self):
            assert self.sum == 6

    class DescribeProd(Context):

        @let
        def prod(self):
            return reduce(lambda a, b: a*b, self.numbers, 1)

        def it_should_be_six(self):
            assert self.prod == 6

It should be fairly easily to spot what’s being tested here: the sum and the product of a sequence of integers.

You will notice few off the simple and powerful concepts in pspecs: Memoized let methods and Parent and children contexts.

Running

pspecs doesn’t provide it’s own runner yet. Instead, it relies on well known test runners like py.test or nose

Running with nose:

>>> nosetests --with-specs my_first_spec.py

Tests narratives

Given a spec file order_spec.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from pspecs import Context

class DescribeOrder(Context):

    def let_quantity(self): return 10
    def let_money(self):
        return Money(10, self.type)

    def let_type(self):
        return 'USD'

    def let_order(self):
        return Order(self.quantity, self.money)

    class DescribeOrderTotal(Context):
        def let_total(self):
            return self.order.total()

        def it_should_account_for_quantity(self):
            assert self.total == 100

        class WithEmptyQuantity(Context):
            def let_quantity(self): return 0

            def it_should_be_zero(self):
                assert self.total == 0

    class DescribeOrderCurrency(Context):
        def let_currency(self):
            return self.order.currency()

        def it_should_be_dollars(self):
            assert self.currency == 'USD'

        class WithEuros(Context):

            def it_should_be_euros(self):
                self.type = 'EUR'
                assert self.currency == 'EUR'

The purpose of any testing library is to make it easy for anybody looking at it to spot quickly what’s being tested.

Looking at the code above and reading only the class names and the it_ method names, we can discover a narrative:

DescribeOrder
    DescribeOrderTotal
        it_should_account_for_quantity
        WithEmptyContext
            it_should_be_zero
    DescribeOrderCurrency
        it_should_be_dollars
        WithEuros
            it_should_be_euros

What pspec does is exactly to allow such kind of narratives to be built easily using simple, yet powerful concepts like contexts, memoized let methods, hooks and isolated tests