This post is part of a series of reviews on the book Head First: Object Oriented Analysis and Design. Check out the Introduction post for a full table of contents along with some initial notes.

We spent the last three chapters basically talking about how we’d develop our Game System Framework, but, honestly, our client doesn’t care about how we do it, he just wants to see it done, or at least some progress, and we can show that progress by doing software development through iterations. Always keep your head on the big picture, and iterate over the pieces of the application until it’s complete.

Considering we’ve already started some work with the game-specific units feature, let’s work it through completion so we’ll have at least something to show our client. Taking a look back at our vision statement in Chapter 6 we can flesh out a few things to do in the Unit class:

  1. Each unit should have properties, and game designers can add new properties to unit types in their own games.
  2. Units have to be able to move from one tile on a board to another.
  3. Units can be grouped together into armies.

We already worked on some code regarding unit properties, but we need to show that code working to our customer, so let’s simply write some tests, which are programs that can prove our existing code regarding some specific feature works. Ruby ships with a cool testing framework, so let’s write our first test file using it:

$:.unshift File.join(File.dirname(__FILE__),'..','lib')

require 'test/unit'
require 'unit'

class UnitPropertiesTest < Test::Unit::TestCase

  def before
    puts "\n\n\nTesting the Unit class...\n"
    @unit = Unit.new('infantry',:hitpoints => 15)
    puts "Created a new unit.\n"
  end

  def test_unit_creation
    before
    assert_equal(@unit.type, 'infantry')
    puts "Unit type: infantry.\n"
    assert_equal(@unit.properties[:hitpoints], 15)
    puts "Unit hitpoints: 15.\n"
  end

  def test_changing_properties
    before
    puts "Changing hitpoints to 20"
    @unit.properties = {:hitpoints => 20}
    assert_equal(@unit.properties[:hitpoints], 20)
    puts "Unit hitpoints: 20.\n"
  end

  def test_null_values
    before
    puts "Changing hitpoints to 25.\n"
    @unit.properties = {:hitpoints => 25}
    assert_equal(@unit.properties[:strength], nil)
    puts "Unit strength: nil.\n"
    assert_equal(@unit.properties[:hitpoints], 25)
    puts "Unit hitpoints: 25.\n"
  end
  
end

Generates:

Testing the Unit class…
Created a new unit.
Unit type: infantry.
Unit hitpoints: 15.

Testing the Unit class…
Created a new unit.
Changing hitpoints to 20
Unit hitpoints: 20.

Testing the Unit class…
Created a new unit.
Changing hitpoints to 25.
Unit strength: nil.
Unit hitpoints: 25.

Finished in 0.0 seconds.
3 tests, 0 failures, 0 errors

Tests don’t have to be complex, they just need to provide a way to show your customer that the functionality in your classes is working correctly, and that’s exactly what we did. Also, you should be sure to test your software for every possible usage you can think of, and that means thinking about the incorrect usage of the software, too. There are a few things you should consider when writing tests:

  • Use descriptive names to define what a specific test will be testing.
  • Each test should test only ONE THING. This way you’ll know exactly the spot where a test is failing, case it starts failing.
  • You must provide an input to a test, and expect an output. This is pretty straight forward (but important to point out), ’cause how the heck will you know if it’s working if you don’t supply/expect information?
  • There will usually be a starting case for your tests. Notice how I put the starting state in the ‘before’ method so I could save up some duplicate code.

So our Unit class looks cool, but it turns out that if we try to group them up in an army we won’t be able to tell them apart if they’re of the same type. Let’s predefine some common properties such as type, id, name, weapons and finally properties, this way game designers will know that each unit will have to explicitely have to define these in their units, and it makes sense too because the concept of units in a game board involves these properties, and this way we’ll be able to tell every unit in the game apart from each other. Notice how we iterated through problems and reevaluated our design decisions.

We could stay keen to the concept of test driven development and go rewriting our tests first, but let’s work the other way around just to see things better (and this is just the practice application, on the next and final chapter we’ll get serious). Here’s our rewritten Unit class diagram:
Chapter 9 - Diagram 1
And here’s the code for it:

class Unit

  attr_accessor :type, :name, :properties

  attr_reader :id, :weapons

  def initialize(id)
    @id = id
  end

  def add_weapon(weapon)
    @weapons ||= []
    @weapons << weapon
  end

end

As we’ve rewritten our code, we should also rewrite our tests to make sure everything is working as expected, and also to show our customer we’re getting things done:

$:.unshift File.join(File.dirname(__FILE__),'..','lib')

require 'test/unit'
require 'unit'

class UnitPropertiesTest < Test::Unit::TestCase

  def before
    puts "\n\n\nTesting the Unit class...\n"
    @unit ||= Unit.new(1)
    puts "Created a new unit.\n"
  end

  def test_unit_creation
    before
    assert_equal(@unit.id, 1)
    puts "Unit id: 1.\n"
  end

  def test_changing_properties
    before
    puts "Setting hitpoints to 20"
    @unit.properties = {:hitpoints => 20}
    assert_equal(@unit.properties[:hitpoints], 20)
    puts "Unit hitpoints: 20.\n"
  end

  def test_null_property_values
    before
    puts "Setting hitpoints to 25.\n"
    @unit.properties = {:hitpoints => 25}
    assert_equal(@unit.properties[:strength], nil)
    puts "Unit strength: nil.\n"
    assert_equal(@unit.properties[:hitpoints], 25)
    puts "Unit hitpoints: 25.\n"
  end

  def test_unit_type
    before
    puts "Setting unit type to infantry.\n"
    @unit.type = 'infantry'
    assert_equal(@unit.type, 'infantry')
    puts "Unit type: infantry.\n"
  end

  def test_unit_name
    before
    puts "Setting unit name to 'John'.\n"
    @unit.name = 'John'
    assert_equal(@unit.name, 'John')
    puts "Unit name: John.\n"
  end

  def test_unit_weapons
    before
    puts "Unit should have no weapons before we add one to it. \n"
    assert_nil(@unit.weapons)
    puts "Unit has no weapons. \n"
    puts "Adding 'Axe' to unit weapons.\n"
    @unit.add_weapon('Axe')
    assert_not_nil(@unit.weapons)
    puts "Unit has at least one weapon. \n"
    assert_equal(@unit.weapons[0], 'Axe')
    puts "Unit has an Axe. \n"
  end
  
end

That is an awful lot of code (mostly because of all the output messages) just to test a few things, in fact, there are frameworks used for testing such as RSpec that do a much better job with fewer lines of code. But at least you got the hang of testing (or should have) and we’re explicitely showing that what we have is working.

Now notice how none of our code is ‘protected’. I mean, if game designers simply use our framework and don’t treat null values, their game will just blow up when it encounters a bunch of nils. This is called programming by contract, we’re trusting developers use our framework to handle these null values, and that they’ll be smart enough to not let things blow up just because of them. But say we got ourselves a client that doesn’t want their developers ‘wasting time’ with this stuff (or they’re just too incompetent), he’s agreeing with us that our software should handle all errors and point out where the problem in case there is one. Since we still haven’t looked through exceptions in any of the reviews, let’s spend a bit of time at it.

First, let’s mess up the way we access properties and do it by hand instead of using an attr_accessor so we can intervene the process with exceptions.


class Unit

  attr_accessor :type, :name

  attr_reader :id, :weapons

  def initialize(id)
    @id = id
  end

  def get_property(key)
    if @properties.nil?
      raise "Are you mad? There are no properties!"
    end

    if @properties[key].nil?
      raise "No value was assigned to this property!"
    else
      @properties[key]
    end
  end

  def set_property(key, value)
    @properties ||= {}
    @properties[key] = value
  end

  def add_weapon(weapon)
    if weapon.nil? || weapon == ''
      raise "You're adding an invalid weapon!"
    else
      @weapons ||= []
      @weapons << weapon
    end
  end

end

We use the syntax ‘raise’ to raise an exception, which by default is an instance of the RunTimeError class. In all cases we’re simply passing an error message to when something goes wrong. Since we rewrote almost the entire class, some of our tests are bound to go wrong, more specifically the ones that deal with properties. Let’s correct them an use assert_raise to make sure our code is doing a good job raising exceptions:

def test_changing_properties
    before
    puts "Setting hitpoints to 20"
    @unit.set_property(:hitpoints, 20)
    assert_equal(@unit.get_property(:hitpoints), 20)
    puts "Unit hitpoints: 20.\n"
  end

  def test_null_property_values
    before
    puts "Setting hitpoints to 25.\n"
    @unit.set_property(:hitpoints, 25)
    puts "Getting unit strength.\n"
    assert_raise RuntimeError do
      @unit.get_property(:strength)
    end
    puts "Unit strength: nil.\n"
    assert_equal(@unit.get_property(:hitpoints), 25)
    puts "Unit hitpoints: 25.\n"
  end

And case you want to treat an exception raised, you can use the rescue clause:

unit = Unit.new(1)

unit.set_property(:hp,25)

begin
  puts unit.get_property(:health)
  rescue RuntimeError
  puts 'Something went wrong.'
end

So, going back to the things we need to finish off we find ourselves iterating to the ‘Units have to be able to move from one tile on a board to another’ clause, but we decided on the last chapter that game designers would be responsible for this, and there’s no reason to change that decision. So we’ll continue to iterate to the clause that mentions unit grouping, let’s finish this off by coding the UnitGroup class and its correspondent tests (oh, don’t worry, we won’t need to program defensively again):

class UnitGroup

  attr_reader :units

  def initialize(units)
    @units = {}
    units.each do |unit|
      @units[unit.id] = unit
    end
  end

  def add_unit(unit)
    @units[unit.id] = unit
  end

  def remove_unit(arg)
    if arg.is_a?(Unit)
      @units.delete(arg.id)
    else
      @units.delete(arg)
    end
  end

  def get_unit(id)
    @units[id]
  end

end

Notice the use of dynamic arguments on the remove_unit method. We wanted to provide a double interface so a game designer could inform an ID or a Unit instance to which he wanted to delete, so instead of creating 2 separate versions of remove_unit we can just check if the argument passed is an instance of Unit, supplying the Hash#delete method the unit ID (since delete only works with hash keys).
Rest should be easy by now, so let’s move on to the tests:

$:.unshift File.join(File.dirname(__FILE__),'..','lib')

require 'test/unit'
require 'unit'
require 'unit_group'

class UnitGroupsTest < Test::Unit::TestCase

  def before
    puts "\n\n\nTesting the Unit Group class...\n"
    @unit1 = Unit.new(1)
    @unit2 = Unit.new(2)
    @list = [@unit1, @unit2]
    @group = UnitGroup.new(@list)
    puts "Created a new unit group with 2 units .\n"
  end

  def test_unit_group_creation
    before
    assert_equal(@list,@group.units.values)
    puts "Unit group has the same units as the list it was given.\n"
  end

  def test_adding_unit_to_group
    before
    unit = Unit.new(3)
    puts "Adding a unit to the group. \n"
    @group.add_unit(unit)
    assert_equal(@group.units.has_value?(unit),true)
    puts "A unit has been successfully added to the group.\n"
  end

  def test_gettting_unit_by_id
    before
    puts "Getting a unit by the ID of 2. \n"
    unit = @group.get_unit(2)
    assert_equal(unit.id,2)
    puts "Retrieved unit's ID: 2"
  end

  def test_removing_unit_by_id
    before
    puts "Removing unit by the id of 2. \n"
    removed = @group.remove_unit(2)
    assert_equal(@group.units.has_value?(removed), false)
    puts "Unit was successfully removed.\n"
  end

  def test_removing_unit_by_unit_instance
    before
    puts "Removing unit by providing a unit instance with the id of 1. \n"
    removed = @group.remove_unit(Unit.new(1))
    assert_equal(@group.units.has_value?(removed), false)
    puts "Unit was successfully removed\n"
  end
  
end

Nothing new here, we just checked to see if every method in the class is working. Remember: testing is a GOOD THING, so start making use of it.