[Q] how to restructure tests for an abstract class?

While practicing BDD on my first-ever BDD project, I have come to a
point where it makes sense to change my original class to an abstract
class and create one (or more) concrete subclasses that implement a
specific method. What is the right way to restructure the tests in
this scenario? Do I leave the existing tests in place and just create
a new spec file that instantiates and tests the concrete subclass?

I think I can continue to instantiate my “abstract” parent as long as
I don’t go near the behavior that will be defined by the concrete
subclasses. Is that the right thing to do?

Thanks for any hints.

cr

On 2/21/08, Chuck R. [email protected] wrote:

While practicing BDD on my first-ever BDD project, I have come to a
point where it makes sense to change my original class to an abstract
class and create one (or more) concrete subclasses that implement a
specific method. What is the right way to restructure the tests in
this scenario? Do I leave the existing tests in place and just create
a new spec file that instantiates and tests the concrete subclass?

I think I can continue to instantiate my “abstract” parent as long as
I don’t go near the behavior that will be defined by the concrete
subclasses. Is that the right thing to do?

Let me respond not so much from a BDD/RSpec point of view, but from a
Ruby language perspective.

Abstract classes aren’t used much in Ruby, most often the job of
providing “abstract” methods is done by modules rather than classes.

Abstract classes are used in languages which define types via
inheritance like C++ or Java , or provide only single inheritance of
implementation like Smalltalk.

In C++/Java you’re pretty much forced to use an abstract class if you
want to provide partial implementation of a type to be filled out by
subclasses. In Smalltalk they’re used to keep things DRY.

In Smalltalk, there are quite a few abstract classes, for example
Collection provides an implementation of methods like select, map,
inject based on subclasses implementing an each method. In Ruby the
same thing is done by having the Enumerable module which can be mixed
into classes which provide an each method.

So the way I’d approach the kind of refactoring you seem to be
implying would be to move the abstract methods in the existing class
into a module included by that class, and then implement other classes
which also include the module.

HTH


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

On Thu, Feb 21, 2008 at 8:09 AM, Chuck R. [email protected]
wrote:

Thanks for any hints.

cr


rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users

I think that shared example groups might be useful.

Pat

Thanks for asking this question. This is exactly what I was going to
write, but you beat me to it!

(Sorry for the top-post; just following the last responder.)

cr

On Mon, Feb 25, 2008 at 6:23 AM, Chuck R. [email protected]
wrote:

Question is, would you duplicate the specs for all the classes that include
a certain module (through shared behaviour for example), or would you use
one set of specs for just the module, and specify that a class should
include that module?

Matthijs - I’d throw the same question back to you. Have you tried
both approaches? How have they worked out for you?

I won’t wait for your answer :). But I am curious about other people’s
experiences with this.

I can tell you this from my own experience - I tend to use shared
groups for this for a couple of reasons. One, I like to see the specs
for each object. Seeing this …

Thing

  • should include the Foo module

in the spec output doesn’t really help me understand what Thing does.

Keep in mind that including a module does not guarantee that the
methods in that module are used. They can be redefined. They can also
behave differently depending on the state of the including object. For
these reasons, I generally don’t really like to specify module
inclusion.

I try to approach this from the perspective of “how do I want
instances of this type to behave?” When I find duplication in
behaviour across types, I may extract a shared example group.

The discipline challenge really comes in when I already have a shared
group and I’m adding a new type and my inner designer is quite certain
that it should include the same module. I can tell you that I’ve gone
at this from two directions - either spec’ing the new type cold or
just including the shared group in the spec for the type and including
the module. Neither route has been perfect every time :slight_smile:

Hope that helps … a little.

Cheers,
David

Question is, would you duplicate the specs for all the classes that
include
a certain module (through shared behaviour for example), or would you
use
one set of specs for just the module, and specify that a class should
include that module?

On Feb 25, 2008, at 8:30 AM, David C. wrote:

both approaches? How have they worked out for you?

I won’t wait for your answer :). But I am curious about other people’s
experiences with this.

I can tell you this from my own experience - I tend to use shared
groups for this for a couple of reasons. One, I like to see the specs
for each object.

I thought I’d share my experiences with the group. Please recall I’m
new at BDD/TDD so I may get some of the terminology wrong or whatever.

So I have a class that, by itself, doesn’t do anything. Concrete
subclasses are necessary to flesh out a few characteristics before the
parent class code can perform its magic. This is an abstract class
which I’m told isn’t the Ruby Way, so I’m looking at turning it into a
module (a topic for another message).

Originally, I was curious how to refactor my classes (and tests). I
decided to write a second concrete subclass to see what kind of
problems I might run into. I figured any problem I encountered was
just more information for me to figure out the correct direction. The
second subclass started out normally enough. I spec’d some behavior
unique to that subclass. So far, so good. Then it occurred to me that
I had no idea if the parent class was really being exercised by my new
tests; turns out it wasn’t being exercised.

To resolve this I started adding some tests to make sure I covered the
original behavior of the parent class. Now I had code duplication. The
parent class tests and my first subclass were duping some of the
same behavior. Looking into the examples directory I saw the concept
of #shared_examples_for.

I refactored my tests using shared examples. Most of these went into
the parent class (perhaps soon to be a module). All the tests in my
subclasses then focused exclusively on the behavior unique to that
specific class while the describe blocks called #it_should_behave_like
for shared behaviors. This DRY’ed the code up considerably.

An added benefit was some test breakage I ran across while refactoring
the tests. My second subclass had some rather tight coupling to the
tests, so when I made it shared the subclass test broke. It forced me
to rethink some of the test and class design to loosen the coupling.
Ultimately it led to a better class api.

So, that’s my rambling summary. Kudos to you if you read this far.

My next goal is to DRY up some of my ‘before (:each)’ blocks. I
continually do the same setup operations across #describe blocks (@buf
= Buf.new; @msg = blah, etc). It looks like I may want to define
subclasses of a parent Spec::ExampleGroup so the subclasses can
inherit some of the #before setup. I’d love to hear experiences from
others on this technique.

Lastly, I thought I’d say a word on the resulting class code. This BDD
project was a learning experience. I rewrote a set of classes that I
had originally written the old-fashioned way; puzzle through the logic
in my head, write the code, and then debug the crap out of it until it
worked. The original classes are rather short (LOC) with only a few
methods (3 or 4). The classes I wrote via BDD are longer, maybe by 40%
in terms of LOC. Plus, I now have around 10 methods none of which
exceeds 5 lines of “real” code. It’s more readable, more logical, and
far easier to subclass. I’m now a believer.

YMMV.

cr