Creating module skeleton from unit tests

Edvard Majakari edvard+news at majakari.net
Fri Mar 4 06:56:12 EST 2005


Greetings, fellow Pythonistas!

I'm about to create three modules. As an avid TDD fan I'd like to create
typical 'use-cases' for each of these modules. One of them is rather large,
and I wondered if it would be easy enough to create a code skeleton out of
unit test module.

Consider the following, though contrived, unit test code snippet:

==========================================================================

import player

class TestClass:

    def setup_method(self, meth):

        self.obj = player.Player('Fred the Adventurer')

    def test_name(self):

        assert self.obj.name == 'Fred the Adventurer'

    def test_inventory(self):

        # player always starts with a dagger
        assert self.obj.inventory == {'dagger': [(1, 4)]}

        # dagger is an initial weapon
        assert self.obj.weapon_type == 'dagger'

        # add sword to player and wield it
        self.obj.add_item('sword', (1, 8))

        # wield the first sword in the backbag
        self.obj.wield('sword')

        assert self.obj.weapon_type == 'sword'

        assert self.obj.inventory == {'dagger': [(1, 4)], 'sword': [(1, 8)] }

    def test_level(self):

        cur_level = self.obj.level

        self.obj.level_up()

        assert cur_level + 1 = self.obj.level

    def test_hitpoints(self):

        start_hp = 30
        sword_damage = 6

        assert self.obj.hitpoints == start_hp

        self.obj.damage(sword_damage)

        assert self.obj.hitpoints == start_hp - sword_damage

==========================================================================

Now it would be nice if I could do

$ python test2skel.py test_player.py

$ cat player.py

class Player:

      def __init__(self, str):         # constructor was called with a string,
                                       # so we create init with str arg

          # self.weapon_type was compared to string and no parenthesis were
          # used, so the best guess is it's an attribute

          self.weapon_type = None

          # ditto for inventory, name and hitpoints

          self.inventory = None

          self.name = None

          self.hitpoints = None


      def add_item(self, str, tpl):
          # add_item() was called with a string arg and a tuple

          pass

      def damage(self, obj):
          # damage() was called with an argument

      def wield(self, obj):
          # wield was called with an arg

      def level_up(self):
          # level_up() was called without args


Now I'm thinking of at least three possible ways to implement that, all of
which may not be very sensible, but what I could come up with:

1. Try to run the test case and capture exceptions. Trying to import player 
   and failing means there is no module player.py. The script could create a
   file player.py. Running the tests again imply there's an attribute Player
   which seems like a method call or class (both are 'called' in the same
   way). Nothing is created, but an ambiguous state is set for Player: it's
   either a class or module method. Later calls for Player instance remove the
   ambiguity and then we know it's a class, and as such the class skeleton
   is created. Proceeding this way a skeleton could probably be created by
   catching Import/Attribute errors and so forth.

2. Parse the test cases module as Python code, and extract all possible
   information therein. Probably the best-working method, but needs Python
   parser and some serious semantic logic. Probably the most obvious but also
   hardest(?) way to do it.

3. Parse the test case as a text file, and extract only necessary
   information. Eg. creating needed module files is probably very easy: for
   practical purposes, you could just grep "import <modlist>" and "from
   <module> import <classes/method>" to find out what module files need to be
   created. Hackish and not elegant but would probably work for custom
   purposes well enough (especially if we restrict ourselves to only lines
   starting with 'import module' statements in the test module). However,
   eg. associating an instance method call with the class used in object
   creation would be quite hard, if the parser is just a simple one.

4. Use a completely different approach, eg. create a module skeleton using a
   custom markup (easily generated from many graphical design tools) and use
   that for creating both the test skeleton and module itself. However, it
   wouldn't be TDD because then you'd be designing the module the usual way

Also remember that the program wouldn't need to be very clever. A strict
coding standard can be assumed, and eg. the fact that all classes are in
CamelCase would help in deducing whether foo.Bar() is a method call or
Bar-class object instantiation (methods are always lower_case_with_underscore,
according to PEP 8 we try to follow).

What do you think? I know that probably the best way to go on is just do it
the old way (ie. code both the test and module by hand), or look for more
intelligent IDEs. 

For what it's worth, I still use most recent XEmacs for coding because it is
just so handy in many areas - sometimes I miss Eclipse-like IDE and I have
even tried pydev for Eclipse (which is a good plugin, btw!), but to this day
I've went back to XEmacs every time because in the end I've always been
lacking something I couldn't find a decent substitute for.


Obligatory note: According to the XP folks, TDD is not as much about testing
as it is about design: "Test Driven *Development*" or maybe even "Test Driven
*Design*". The problem can be solved more easily if you design the module
skeleton first, then the tests and then the logic for the skeleton
- you would be creating tests before the code, but many people wouldn't regard
it as TDD then.

--
# Edvard Majakari		Software Engineer
# PGP PUBLIC KEY available    	Soli Deo Gloria!

$_ = '456476617264204d616a616b6172692c20612043687269737469616e20'; print
join('',map{chr hex}(split/(\w{2})/)),uc substr(crypt(60281449,'es'),2,4),"\n";



More information about the Python-list mailing list