Back to complex actions and monads

This seems to be a very recurring theme recently: actions that possible have multiple subactions and how should I combine them. Whole action subsystem of the game is probably the one that has gone through most different kinds of revisions. In a sense it’s fun, but on the other hand, sometimes I wish it were already good enough and I could work on something else. Coding how to walk around the map gets boring after couple of iterations after all.

Continue reading

Little level dsl tricks

I have been tinkering with how levels are configured and made some minor changes. I’m trying to keep things relatively simple, but at the same time I want to have configuration code to look more like a configuration file than a function definition.

Below is shown a configuration for area called “first gate”. This is the area where the game starts. Two requires at the top are used to signal that I want to use certain macros. (level-config-dsl) is just a cute way to have pile of imports without cluttering the code. After that follows a list of levels, with just single entry. That entry first contains name and description of the level and rest is just configuring how it connects to other levels and how it is going to be generated. I made a post earlier explaining how the level generation works.

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.

Cutesy and poison

Some time ago I wrote couple simple BDD-tests for combat. Today while cleaning up some test code I moved two poison related tests over to BDD side. It was nice to see that quite lot of what I had written for previous tests could be reused for new ones. I only had to create code for poison specific things.

Below is the code. First test is testing that poison actually damages character. Second one is for testing that poison can kill character and dead don’t hang around in the game world.

def test_poison_causes_damage(self):
    """
    Test that triggered poison will damage character
    """
    Pete = strong(Adventurer())

    affect(Pete, with_(weak_poison()))

    assert_that(Pete, has_less_hit_points())

def test_character_can_die_from_poisoning(self):
    """
    Test that character with less than 1 hit points is removed from play
    """
    Uglak = weak(Goblin())
    room = Level()
    place(Uglak, middle_of(room))

    affect(Uglak, with_(potent_poison()))

    assert_that(Uglak, is_dead())
    assert_that(Uglak, is_not_in(room))

Poor Uglak bites the dust again and Pete continues adventuring.

I probably need to soon start thinking on how to structure code in cutesy (BDD-oriented DSL for pyherc). Currently all the code is in a single file, but eventually I have to start splitting it up. I’m undecided still, if I want to do imports from top level of cutesy package, or from individual modules. Both have their advantages. Top level is nice, because I can move around code inside cutesy without breaking anything. Individual modules is nice, because then I can have identical functions, with different meaning.

Domain Specific Languages by Fowler

I finished reading Domain Specific Languages by Fowler some time ago. The reason I’m interested in DSLs in general is because I’m looking how to use them in testing. Adding a layer of abstraction on top of all those little gritty technical details helps to move focus to higher level, namely to problem domain. Instead of specifying that in order the test to pass, these two variables how to be equal, I can specify the same thing as an concept, using language that is more¬†close to problem domain.

Book is pretty hefty and has quite lot of information inside of it. The focus is on the procedural languages, because Fowler feels that he does not have enough expertise on functional languages to write about them. Some of the ideas presented in the book work on the functional side and some can be adapted. Some are so specific to procedural languages though, that they aren’t useful on the functional side.

Book is divided in two sections. First section is about what DSLs are, how they are used and what kind of techniques can be used to contruct them. It constantly refers to second section, which lists all the patterns used in the book in short and clearly written format. In the beginning this referring back and forth was a bit annoying, but after patterns started sinking in, reading the first section was more enjoyable. In the end I liked the format though, since I can easily go back and check patterns, without having to hunt through the whole book for correct page.

Book was interesting to read, but left a feeling that there are so much more to write about DSLs. Also, it left a bit vague feeling in times, probably because the subject is so new to me. Good read in any case, and very good addition to me desk library.

Testing with helpers and inventing your own language (kinda)

I was reading about domain specific languages and one of the interesting applications is framework for behaviour driven development (there are lots more of course). With Python it should be relatively easy to construct language like that, but I chose a little bit different approach on a test that I wrote as an exercise:

Pete = strong(Adventurer())
Uglak = weak(Goblin())

place(Uglak, middle_of(Level()))
place(Pete, right_of(Uglak))

make(Uglak, hit(Pete))

assert_that(Pete, has_less_hit_points())

This hides all the nasty details about factories, parameter classes and random number generator stubbing, leaving only the most important parts of the test visible. There isn’t a setup method to set up the testcase, because in this case it probably would make test harder to understand (and granted, there isn’t exactly that many lines of code).

Pete and Uglak are both instances of Character class. One of them is modified to be strong, while other is weak. Both are located on an empty level, Uglak standing in the middle and Pete right next to him (on right side).

When Pete gets hit by Uglak, his hit points should go down (not much, he’s not going to die from a single weak hit).

The visible code in the test case is considerably shorter than what it would have been if I had written it a year ago. At that time all the details of the test would have been visible in the test case (or in the setup) and reading through it later would have confused me.

I could refine the code behind the scenes a lot more. One idea would sprinkle it generously with various asserts that would check if operation was successful. When the test is accidentally run on a level consisting of solid rock, trying to place Uglak in the middle of it could detect this by assert and announce that “Tried placing Uglak in the middle of the level, but there was solid rock there”.

By defining more verbs (place, make, hit), nouns (Adventurer, Goblin) and those others which names escape me (right_of, middle_of) I could build a dictionary that can be used in high level testing. Tests with high level of abstraction would define how system is supposed to work (outer quality) and lower level tests would guide the design and ensure that the code works (internal quality).