This is a pre-production deployment of Warehouse, however changes made here WILL affect the production instance of PyPI.
Latest Version Dependencies status unknown Test status unknown Test coverage unknown
Project Description

make-it-easy

Introduction

make-it-easy is a tiny framework that makes it easy to write Test Data Builders in Python. The framework is a port of the Java make-it-easy by Nat Pryce

Test Data Builders are described in the book Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce. This library lets you write Test Data Builders with much less duplication and boilerplate code than the approach described in the book.

Installation

make-it-easy can be installed using the usual Python packaging tools. It depends on distribute, but as long as you have a network connection when you install, the installation process will take care of that for you.

Usage

Basic

Consider the following class hierarchy. This hierarchy illustrates a couple of complicating factors: there is an abstract base class Fruit and there is a property (ripeness) that is not set via the constructor but by an operation of the Fruit class.

class Fruit(object):
    def __init__(self):
        self._ripeness = 0.0

    def ripen(self, ripeness):
        self._ripeness = ripeness

    @property
    def is_ripe(self):
        return self._ripeness >= 0.9


class Apple(Fruit):
    def __init__(self, num_of_leaves):
        super(Apple, self).__init__()
        self.num_of_leaves = num_of_leaves


class Banana(Fruit):
    def __init__(self, curve):
        super(Banana, self).__init__()
        self.curve = curve

Doing so in the style documented in Growing Object-Oriented Software, Guided by Tests would look like this:

class AppleBuilder(object):
    def __init__(self):
        self._ripeness = 0.5
        self._leaves = 2

    def with_ripeness(self, ripeness):
        self._ripeness = ripeness
        return self

    def with_leaves(self, leaves):
        self._leaves = leaves
        return self

    def but(self):
        return AppleBuilder() \
            .with_ripeness(self._ripeness) \
            .with_leaves(self._leaves)

    def build(self):
        apple = Apple(self._leaves)
        apple.ripen(self._ripeness)
        return apple


class BananaBuilder(object):
    def __init__(self):
        self._ripeness = 0.5
        self._curve = 0.1

    def with_ripeness(self, ripeness):
        self._ripeness = ripeness

    def with_curve(self, curve):
        self._curve = curve

    def but(self):
        return BananaBuilder() \
            .with_ripeness(self._ripeness) \
            .with_curve(self._curve)

    def build(self):
        banana = Banana()
        banana.ripen(self._ripeness)
        banana.curve = self._curve
        return banana

apple_with_two_leaves = AppleBuilder().with_leaves(2)
ripe_apple = apple_with_two_leaves.but().with_ripeness(0.95)
unripe_apple = apple_with_two_leaves.but().with_ripeness(0.1)

apple1 = ripe_apple.build()
apple2 = unripe_apple.build()

While doing it with make-it-easy can be easy as that:

from make_it_easy import *

def apple(leaves=2, ripeness=0.0):
    an_apple = Apple(leaves)
    an_apple.ripen(ripeness)
    return an_apple


def banana(curve=0.1, ripeness=0.0):
    a_banana = Banana(curve)
    a_banana.ripen(ripeness)
    return a_banana

apple_with_two_leaves = an(apple, with_(2, 'leaves'))
ripe_apple = apple_with_two_leaves.but(with_(0.95, 'ripeness'))
unripe_apple = apple_with_two_leaves.but().with_(0.1, 'ripeness'))

apple1 = make(ripe_apple)
apple2 = make(unripe_apple)

As you can see, with Make It Easy you have to write a lot less duplicated and boilerplate code.

Value Donors

Primitives / Makers

A value donor is any primitive (‘Bob’ / 3 / False / etc.) or a Maker (the returned object from the a, an functions). All these can be used as the value in with_. For instance a customer Maker can be a donor in order Maker. It’s important to notice that if a Maker is used as a donor, a new instance will be created every time:

a_customer = a(customer, with_('Bob', as_('name')))
an_order = an(order, with_(a_customer, as_('customer')))
my_order1 = make(an_order)
my_order2 = make(an_order)
assert_that(my_order1.customer, is_(instance_of(Customer)))
assert_that(my_order2.customer, is_(instance_of(Customer)))
assert_that(my_order1.customer, is_not(same_instance(my_order2.customer)))  # two different instances!!!

The Same Value Donor

Sometimes you will need to share the same value while making new data objects, this can be done using the_same value donor. In the following example both my_order1 and my_order2 will have the same customer instance:

a_customer = a(customer, with_('Bob', as_('name')))
an_order = an(order, with_(the_same(a_customer), as_('customer')))
my_order1 = make(an_order)
my_order2 = make(an_order)
assert_that(my_order1.customer, is_(same_instance(my_order2.customer)))

Custom Donors

In order to create a custom donor, you will simply need to implement the Donor interface.

class IndexDonor(Donor):
    def __init__(self):
       self._count = itertools.count()

    @property
    def value(self):
       return next(self._count)

an_indexed_thing = an(an_indexed_thing, with_(IndexDonor(), as_('index')))
indexed_thing1 = make(an_indexed_thing)
indexed_thing2 = make(an_indexed_thing)
assert_that(indexed_thing1.index, is_(equal_to(0)))
assert_that(indexed_thing2.index, is_(equal_to(1)))

Sequences Donors

Sometimes we want values to be allocated from a sequence, so we can predict their values or understand where data has come from in test diagnostics. make-it-easy lets you define a fixed or repeating sequence of values.

A fixed sequence is defined by the from_ function which expects an iterable:

letters = from_("abc")
assert_that(letters.value, is_(equal_to("a")))
assert_that(letters.value, is_(equal_to("b")))
assert_that(letters.value, is_(equal_to("c")))

A fixed sequence of values will fail if asked to provide more elements than are specified when the sequence is created. A repeating sequence will start back at the beginning of the sequence when all elements are exhausted:

letters = from_repeating("ab")
assert_that(letters.value, is_(equal_to("a")))
assert_that(letters.value, is_(equal_to("b")))
assert_that(letters.value, is_(equal_to("a")))
assert_that(letters.value, is_(equal_to("b")))
assert_that(letters.value, is_(equal_to("a")))
assert_that(letters.value, is_(equal_to("b")))

Both fixed and repeating sequences can be created from any iterable (tuple / list / set / dict / etc.).

Calculated Sequences

If we do not want to explicitly specify a sequence of values, we can use some convenient base classes to help us calculate each element of the sequence. An IndexedSequence calculates each element of the sequence from its integer index, starting at zero.

class FTagSequence(IndexedSequence):
    def _value_at(self, index):
        return 'f' + "'" * index

f_tag_sequence = FTagSequence()
assert_that(f_tag_sequence.value, is_(equal_to("f")))
assert_that(f_tag_sequence.value, is_(equal_to("f'")))
assert_that(f_tag_sequence.value, is_(equal_to("f''")))

A ChainedSequence calculates each element of the sequence from the element that preceded it.

class FTagChainedSequence(ChainedSequence):
    def _first_value(self):
        return 'f'

    def _value_after(self, prev_value):
        return prev_value + "'"

f_tag_sequence = FTagChainedSequence()
assert_that(f_tag_sequence.value, is_(equal_to("f")))
assert_that(f_tag_sequence.value, is_(equal_to("f'")))
assert_that(f_tag_sequence.value, is_(equal_to("f''")))
Release History

Release History

1.0.3

This version

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.0.2

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.0.1

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

1.0

History Node

TODO: Figure out how to actually get changelog content.

Changelog content for this version goes here.

Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Show More

Download Files

Download Files

TODO: Brief introduction on what you do with files - including link to relevant help section.

File Name & Checksum SHA256 Checksum Help Version File Type Upload Date
make-it-easy-1.0.3.zip (12.3 kB) Copy SHA256 Checksum SHA256 Source Nov 24, 2013

Supported By

WebFaction WebFaction Technical Writing Elastic Elastic Search Pingdom Pingdom Monitoring Dyn Dyn DNS HPE HPE Development Sentry Sentry Error Logging CloudAMQP CloudAMQP RabbitMQ Heroku Heroku PaaS Kabu Creative Kabu Creative UX & Design Fastly Fastly CDN DigiCert DigiCert EV Certificate Rackspace Rackspace Cloud Servers DreamHost DreamHost Log Hosting