Codifying some facts about pulling things

I was travelling and wasn’t able to hack code as much as I would liked to (and on top of that, I wanted to play some computer games too). But I did manage to push next feature forward just a little bit and write some BDD specifications for it. It’s a bit odd to write BDD specs for a single person project, but I view doing that as a good training and a nice favour to my future self.

Continue reading

Behave

Behave is a tool for behaviour driven development for Python. So far I have been using hand rolled DSL for some of the behaviour tests, but decided to give behave a go today.

I had earlier written a test with Cutesy and decided to write same tests with behave in order to test the differences:

    def test_dropping_item(self):
        """
        Items dropped by character should end on the floor
        """
        dagger = Dagger()
        Uglak = Goblin(carrying(dagger))
        place(Uglak, middle_of(Level()))

        make(Uglak, drop(dagger))

        assert_that(Uglak, has_dropped(dagger))

Following is my feature specification:

Feature: Dropping items
  as an character
  in order to manage my inventory
  I want to drop items

  Scenario: drop item
     Given Pete is Adventurer
       And Pete is standing in room
       And Pete has dagger
      When Pete drops dagger
      Then dagger should be in room
       And dagger should be at same place as Pete
       And dagger should not be in inventory of Pete
       And time should pass for Pete

Nothign fancy, just a description of the feature and single scenario. It is quite a bit more verbose than the Cutesy-version, but I would think it is also quite a bit more readable for non-programmers. Of course behind the scenes there is some code to implement various steps:

@given(u'{character_name} is Adventurer')
def impl(context, character_name):
    context.characters = []
    new_character = Adventurer()
    new_character.name = character_name
    context.characters.append(new_character)

Not that many lines to implement single step (ok, it’s quite quick and dirty implementation, but still). Interesting point here is that I could reuse function Adventurer, which was originally defined for Cutesy.

Same trend continues as I define more and more steps. Cutesy makes writing tests with behave really nice and easy. Fowler mentioned something along lines that after defining an internal DSL, you can use the same implementation as a basis of external DSL. Basically you just need to add an parser. And that’s exactly what I’m doing here.

Behave has possibility to define parameters for tests. This allows reusing same steps in different tests. In the example here, I have two parameters, character name and item name. They match to character and item created in earlier tests.

@when(u'{character_name} drops {item_name}')
def impl(context, character_name, item_name):

    characters = [x for x in context.characters
                  if x.name == character_name]
    character = characters[0]
    
    items = [x for x in context.items
             if x.name == item_name]
    item = items[0]

    make(character, drop(item))
    assert True

When I run behave, I get following output:

Feature: Dropping items # features\dropping.feature:1
  as an character
  in order to manage my inventory
  I want to drop items

  Scenario: drop item                             # features\dropping.feature:6
    Given Pete is Adventurer                      # features\steps\items.py:8
    And Pete is standing in room                  # features\steps\items.py:16
    And Pete has dagger                           # features\steps\items.py:29
    When Pete drops dagger                        # features\steps\items.py:42
    Then dagger should be in room                 # features\steps\items.py:56
    And dagger should be at same place as Pete    # features\steps\items.py:67
    And dagger should not be in inventory of Pete # features\steps\items.py:79
    And time should pass for Pete                 # features\steps\items.py:91


1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
8 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.0s

Behave is pretty strong tool and really easy to use. It took me around half an hour to install it, read tutorial and write first pyherc test (which was ugly, but it worked). I don’t know if I want to use behave more in testing pyherc, or if I continue with DSL-route. Tests written with behave are easier to read for others, but pyherc is currently one man project.

Rewriting code to drop items

Long ago (like, last year), I wrote code that allowed characters to drop items they are carrying. That was before I came up with the new action system though and I never got around to actually update that corner of the code. Recently I started working with a new user interface written with PyQt4 and decided to upgrade drop code as a part of writing new inventory system.

I started writing a simple BDD-test with Cutesy. Nothing too fancy, but enough to test that the goblin can drop item it is carrying:

    def test_dropping_item(self):
        """
        Items dropped by character should end on the floor
        """
        dagger = Dagger()
        Uglak = Goblin(carrying(dagger))
        place(Uglak, middle_of(Level()))

        make(Uglak, drop(dagger))

        assert_that(Uglak, has_dropped(dagger))

Few new words had to be defined for this to work: carrying, drop and has_dropped. Obviously running the test failed and I had to implement some code to get things working. However, instead of using BDD-test to guide me through writing the code, I used it only as a definition of goal. Now I had place to start (character with item, who can not drop it) and goal (character, who has dropped an item). Small steps that would take me there, I defined as regular unit tests (some are shown below).

class TestDropAction(object):
    """
    Tests for dropping item
    """
    def __init__(self):
        """
        Default constructor
        """
        super(TestDropAction, self).__init__()

        self.item = None
        self.level = None
        self.character = None
        self.action_factory = None

    def setup(self):
        """
        Setup test case
        """
        self.level = LevelBuilder().build()
        self.item = ItemBuilder().build()

        self.character = (CharacterBuilder()
                                .with_item(self.item)
                                .with_level(self.level)
                                .build())

        self.action_factory = (ActionFactoryBuilder()
                                    .with_inventory_factory()
                                    .build())

    def test_dropped_item_is_removed_from_inventory(self):
        """
        Test that dropped item is removed from inventory
        """
        self.character.drop_item(self.item,
                                 self.action_factory)

        assert_that(self.item,
                    is_not(is_in(self.character.inventory)))

    def test_dropped_item_is_added_on_level(self):
        """
        Test that dropped item ends up on level
        """
        self.character.drop_item(self.item,
                                 self.action_factory)

        assert_that(self.item.level,
                    is_(equal_to(self.level)))

This is my preferred way of doing test driven development. Have one high level test to define your goal, that can be used to communicate with business owners. Have many low level tests that define technical implementation and guide you from start to finish in small steps. When something fails in the system later, we probably should end up with two errors: first one tells that dropping items is not possible anymore, second one tells that dingusX is broken and returns 1 instead of 2. Again, first one can be used to show general idea about the problem (it’s easier to talk with domain terms than with technical terms), while second one is tool for developers to pinpoint faul point.