We left our project with an integration test that doesn’t pass (yet), and a unit test for the first piece of business
logic revealed by this integration test: the character races as “types” for our
Character objects. It is now time to
make the unit test pass, which should drive us to an implementation of the
Race class, and then move on to the next
encounter in our integration test.
Our unit test, at the moment, looks like this:
And, as expected, it fails. More precisely, once we add a placeholder
Race class to satisfy the
it fails with this interesting error:
class or module required.
Back in the previous chapter, I said that a true mockist would probably not use the
Race class in the example, but
instead something simpler, like
Object. Well, this wasn’t completely accurate. Because we want whatever object
is passed to
#choose_race to define what a
Character object “is”, this object has to be either a class or a module,
as the error message tells us.
Let’s pause for a second. Our test is driving our design. Writing it, we discovered that we want characters races
to be instances of some kind of
Race class (
race=Race.new). But, at the same time, our test lead us to a design
where these instances must be classes or modules themselves (
assert_kind_of race, with
a class or a module). Can an instance also be a class (or a module)?
Of course it can! This is one of the great (and elegant, and almost magical) things about Ruby: classes are instances,
too – namely, instances of the
Class class. By the same principle, modules are instances of the
Module class, or a
subclass of it. Therefore, we can move on to the next failure in our test by ensuring that
Race.new returns either a
class or a module. The simplest way to do that would to make
Race inherit from either one – except that Ruby doesn’t
Class, so the only option left is to have
Race inherit from
Module, so that
a (new) module.
We’ve successfully failed – meaning that we’ve successfully moved on to a different failure. But this one is a bit cryptic.
And how could our object be both “a kind of”
Character and “a kind of”
Race? Object in Ruby can only be of a single
Without diving too deep in the (marvellous) object model of Ruby, let’s make a slight detour. The
matcher relies on
Object#kind_of?, which is defined
kind_of?(class) → true or false
trueif class is the class of obj, or if class is one of the superclasses of obj or modules included in obj.
This is very accurate but maybe a bit obscure, if you’re not familiar with the way classes, modules and instances
work in Ruby. Another way to define
kind_of? could be:
trueis class is among the ancestors of obj’s [singleton] class.
Let’s ignore the word in brackets for now. In Ruby, we know that each object has a class; this class, like all
classes, inherits from another class, which itself inherits from another class, and so on until this chain of
BasicObject, “the parent class of all classes in Ruby”.
We can check this out by looking at the ancestors of the class of the
character object in our test. We know
that this object’s class is
SteelVellum::Character, so we can do it like this:
Ignoring the first item in this array (which is the interrogated class itself), we see the list of classes1 from
Kernel and, eventually,
BasicObject. For our test to pass, and
assert_kind_of race, character.character to be true, we need to somehow add
race to this list of
We cannot do that by making our
race object a parent of the
Character class – first because it would make no sense
from a business logic perspective (characters are not character races), but more importantly because we’ve already
race is a
Module, and modules cannot be inherited from.
However, like classes, Ruby modules can be part of the ancestors chain of a class – in fact, in the ancestors list
Kernel is actually a module, not a class. As explained in
the definition of
Object#kind_of?, included modules also count as ancestors. But how could we include this
module in the class of our
A single-use class
Once again, the
character object is an instance of
Character. So, a naive way to have it also be “a kind of”
would be to call
Character.include race. But then, all instances of
Character would also have
race in their ancestors.
All characters would be of the same race, which is not what we want.2
What we want is for this
race module to be included in the class of our
character instance, but only for this
instance. And we can do that thanks to
Module#extend and the elegant magic of the singleton class.
By extending the instance with the module instead of including the module in the class, we’ll have what we want. To see this in action, let’s temporarily hack our test:
Or test passes! But how come?
When we called
character.extend race, Ruby did something clever. It created a new class, anonymous, and had it
Character. It also included
race into this new class, and then had
character inherit from it. Because
it inherits from
Character, this anonymous class behaves exactly as
Character, but it is specific to the
object. (And it includes
race, which is the whole point.)
There is no formal name for this kind of object-specific, anonymous class. Some used to call it “eigenclass”, others
“ghost class”, but nowadays, it is most often named singleton class3. In fact, this class can be reached (and
created on-the-fly, if necessary) by calling
Let’s launch an IRB console and compare the ancestors of this singleton class, for a given
Character instance, before
and after extending a
So, there you have it. Even though Ruby objects can only be instances of a single class, they can inherit traits from any number of modules, and don’t have to share these inheritances with any other object, thanks to the existence of a singleton class.
Now that we know how to have characters be of a given race, and why this is even possible in the first place, let’s remove the hack from our test and implement things properly:
Back to the outer loop…
Our unit test now passes – we’ve closed the small loop. Let’s go back to the big loop (the integration test) and see where the next failure leads us.
This was to be expected –
CharacterCreation#choose_race must be passed a module now, but
MountainDwarf is still a
slimed class. Let’s change that.
Moving on, we can rerun the integration test and figure out what other missing piece of our library we should build now.
This is a more interesting failure! According to the test, making Bruenor a Mountain Dwarf should automatically give him
:medium size, but at the moment,
Character#size always returns
nil (since we didn’t bother actually implementing
the method’s body). Let’s remedy that.
Because this failure reveals a missing piece of business logic, we must start a new small loop, and design the implementation of this unitary feature through one or more unit tests.
Hooks in you
For a start, let’s simply isolate the failing assertion from the integration test into an unit test:
Covered by our unit test, let’s think about a way to make it pass. The test tells us that, once a
is extended by the
MountainDwarf module, its
#size method should return
:medium instead of
nil. When a module
extends an object, the methods defined inside this module are added to the instance methods of the object’s singleton
class, so one way to make our test pass would be to redefine
#size in the
However, while perfectly fine in general, I’m not too fond of this approach in this specific situation. That is because a character’s size is more data than behavior. I’d rather store this information in an instance variable than have it being returned by a method4.
Thankfully, Ruby gives us another trick to reach our goals: hook methods. These are methods that, if defined,
get called we certain events happen in an object’s lifetime. For example,
#method_missing is a well-known hook
method that is called when an object (or rather: a module or a class) receives a call to a method that neither
it not any of its ancestors define. In our case, we’ll make use of the
This method is called whenever a module extends an object. We can use it to change the value of the character’s
instance variable – in practice, giving it a default value, which the
Character instance will then be free to change,
if need be. (After all, our Dwarf could one day drink a magical potion and grow a size or two.) This is what using
#extended hook looks like:
Of course, for the
character.size = :medium instruction to work, we need to give accessors to the
Now our test passes. We can close this small loop and go back, once again, to the big one by running (yet again) the integration test. It now fails because of the next character trait that a race is supposed to give a default value to:
This time, it is
Character#speed that doesn’t return the expected value. We’ll proceed as exactly like we have with
#size – adding a unit test, watching it fail, making it pass, and then moving back to the integration test. And after
that, we’ll have
Character.darkvision to fix. In the end, this is what our
MountainDwarf class and its tests will be:
Stepping away from BDD
Normally, keeping with our back-and-forths between the integration tests and the unit tests, our next step should probably be
have to do with
ability_score_increases. However, once again, I’d like to take a step back and consider
our recent work.
We’ve implemented the behavior of the
Races::MountainDwarf instanciated modules, because this is what our tests have
covered. But we know that other races will eventually be covered by the library, and we know that they, too, will
assign a size, a speed and a darkvision range to the characters. So, even though we don’t have any test to lead us
there yet, we can safely assume that making this piece of business logic a bit more generic is valuable.
In practice, this means that any subclass of
Race should be able to assign values to a
@darkvision instance variables, and the assigned values would depend on the subclass itself. This is rather easy
to write tests for.
First, we need to be able to define the values that a race will assign:
Then, we need to ensure that using the race to extend a
Character assigns these values. We can simply cannibalize
the tests for
MountainDwarf; but for the sake of conciseness, we’ll squash the 3 tests into a single one with
The implementation is pretty straightforward too, except for one subtlety:
#extended hook method must be defined in the class (singleton or not) of the object on which it will be called.
This is why, when its definition was in the
MountainDwarf class, it was sent to
self. (In other words:
was defined as a class method of
MountainDwarf). However, since we’re moving this definition up to the
class of all races modules, the
#extended must now be defined as an instance method5 of
(Note also that we’ve also added default values in the initializer, even though we didn’t write tests for that, and therefore have no idea if this is legitimate design or not – we’re freewheeling! 🤘)
Hidden edge cases
Here is a secret about BDD: since it’s about letting the expected behavior drive the design, edge cases – in other words: unexpected behavior – can slip through. Which is why it is important to consider these edge cases when working at the unit test level, where they are easier to think about.
In our case, even though we’ve kept saying that a character’s race gives it default values for some traits, we haven’t
tested for the (unlikely) situation where some would have already been defined before the race was assigned. So let’s
add that. And while we’re at it, let’s cover another edge case: using a race module to extend an object which is not
an instance of the
The final implementation is quite easy:
Now that the logic for assigning default values to a character’s racial traits is moved up to the
Race class from
MountainDwarf inherits, we can clean up our previous work, by deleting the now redundant unit tests in
MountainDwarfTest, and the logic from
And this is it (for now)! We’ve successfully implemented the first actual piece of logic in our library, which is actually quite a lot:
- We can define a character race, or at least 3 of its traits for now.
- These traits are automatically assigned to a character when their race is chosen during character creation.
- For developers who’ll eventually use our library, assigning a
Characterobject gives it some kind of “type”, which is probably a false good idea, but fun nonetheless.
We can now return to our big loop, once again, and see what the DM of BDDing has for us in the next installment of this series!
Roughly speaking. ↩
Feel free to try this out in an IRB console: create 2 instances of
Character, create a new
Race, include it in
Character.include the_new_raceand see that both the instances now “are” also of this race. ↩
Technically, even if store in an instance variable, the value will be returned by a method (namely, a reader accessor), but hopefully you see what I mean. ↩
As an exercice, can you guess what would happen, and why, if within the
Raceclass we’d write
def self.extended? ↩