Forum: RSpec What's the best way to test inherited behavior?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Hongli L. (Guest)
on 2008-10-09 21:22
(Received via mailing list)
I currently have a base class and 2 subclasses. I'm struggling with
finding the best way to test them. This is the current situation.

The base class is called AbstractServer. It's not really abstract
because it can be instantiated and used, but some important methods are
defined as no-ops, and are meant to be overrided by child classes. The
spec for AbstractServer looks like:

   describe AbstractServer do
     it_should_behave_like "AbstractServer-like behavior"

     ... AbstractServer tests ...
   end

For the child classes, their specs look like this:

   describe ApplicationSpawner do
     it_should_behave_like "AbstractServer-like behavior"

     ... ApplicationSpawner-specific tests ...
   end

   describe FrameworkSpawner do
     it_should_behave_like "AbstractServer-like behavior"

     ... FrameworkSpawner-specific tests ...
   end

However, this looks very ugly. 'it_should_behave_like
"AbstractServer-like behavior"' doesn't read like a normal sentence, and
saying that AbstractServer has AbstractServer-like behavior is
redundant.

What's the best way to solve this? What are good practices for testing
inherited behavior? Should I be testing my child classes for parent
class behavior at all? Right now I'm doing it anyway in order to detect
bugs that I might have missed otherwise.
David C. (Guest)
on 2008-10-09 22:12
(Received via mailing list)
On Thu, Oct 9, 2008 at 12:20 PM, Hongli L. 
<removed_email_address@domain.invalid> wrote:
>
>
> What's the best way to solve this? What are good practices for testing
> inherited behavior? Should I be testing my child classes for parent class
> behavior at all? Right now I'm doing it anyway in order to detect bugs that
> I might have missed otherwise.

Not sure why what you're doing works at all, but try this (which is
how it should work):

describe "AbstractServer", :shared => true do
  ...
end

describe ApplicationSpawner do
  it_should_behave_like "AbstractServer"
  ..
end

etc
Matt W. (Guest)
on 2008-10-09 22:51
(Received via mailing list)
On 9 Oct 2008, at 18:20, Hongli L. wrote:

> I currently have a base class and 2 subclasses. I'm struggling with
> finding the best way to test them. This is the current situation.
> What's the best way to solve this? What are good practices for
> testing inherited behavior? Should I be testing my child classes for
> parent class behavior at all? Right now I'm doing it anyway in order
> to detect bugs that I might have missed otherwise.

I've been meaning to blog about this, as I recently had an epiphany
about how brilliantly rspec handles this, IMO.

I actually do it pretty much the way you're doing it, but I don't
think it's ugly at all.

To me, the abstract superclass is just an implementation detail of the
concrete subclass - I don't need to or want to care that
implementation detail when I'm writing specifications about how the
concrete class should behave. Therefore while the behaviour which is
common between it and other classes that have the same superclass does
tend to end up in shared example groups, that's just because I'm
keeping my code nicely factored - the same specs would still be valid
if I copied and pasted the superclass code out into the two subclasses
and collapsed the hierarchy, or moved them into modules, or whatever.

It's a subtle shift, but if you try to let go of the implementation,
and think about the *behaviour* when you name your example groups,
you'll find they start to read more naturally.

In your case, the two concrete servers can just say

  it_should_behave_like "a server"

and pull in the specifications for the behaviour that is common to both.


At songkick, for example, we're modelling bands playing gigs. We have
an abstract Event superclass, subclassed by Concert and Festival. The
spec for Event reads like this:

describe Event do
  it "should be abstract"
       lambda { Event.new }.should raise_error(TypeError)
  end
end

Then the two subclasses mix in a module containing the shared example
groups, and at the top, they say

describe Concert do
  extend SK::Spec::Models::ExampleGroups #shared example groups for
models live in here
  it_should_behave_like "an event"

  # special concert behaviour spec'd here...

I've tried to find a pattern I was comfortable with for this with
xUnit tests, and never managed it. For me, rspec's solution fits the
problem like a glove.

HTH,
Matt
Hongli L. (Guest)
on 2008-10-09 23:09
(Received via mailing list)
David C. wrote:
> Not sure why what you're doing works at all

Oh oops, I forgot to mention that I also have 'shared_examples_for
"AbstractServer-like" behavior'.
Pat M. (Guest)
on 2008-10-09 23:19
(Received via mailing list)
I've written [1] about using shared example groups to do this sort of
things.  You're already using them :)  but maybe you can still get
something out of that post.

What specifically don't you like about this solution?

Pat

[1]
http://evang.eli.st/blog/2008/5/14/refactoring-wit...
Hongli L. (Guest)
on 2008-10-09 23:36
(Received via mailing list)
Pat M. wrote:
> I've written [1] about using shared example groups to do this sort of
> things.  You're already using them :)  but maybe you can still get
> something out of that post.
>
> What specifically don't you like about this solution?

What Matt proposed is very nice. :) I was struggling with finding a good
way to name my specs.

However, by calling the shared example "a server" I can still end up
with weird names:

   describe FrameworkSpawner
     describe "when in conservative spawning mode"
       before :each do
         ..
       end

       it_should_behave_like "an AbstractServer"
     end

     describe "when in smart spawning mode"
       before :each do
       end

       it_should_behave_like "an AbstractServer"
     end
   end

So I can end up with a weird sentence like:
"FrameworkSpawner when in smart spawning mode an AbstractServer raises
AlreadyStarted if the child process is still running"


Shared example groups *are* a blessing. Just the constructed sentences
bother me sometimes. :) I'm also wondering whether it's a good idea to
test my child classes like this, or whether I should test the parent
class separately, and not test the child classes for the parent class's
behaviors.
Matt W. (Guest)
on 2008-10-10 11:53
(Received via mailing list)
On 9 Oct 2008, at 20:35, Hongli L. wrote:

> Pat M. wrote:
>> I've written [1] about using shared example groups to do this sort of
>> things.  You're already using them :)  but maybe you can still get
>> something out of that post.
>> What specifically don't you like about this solution?
>
> What Matt proposed is very nice. :) I was struggling with finding a
> good way to name my specs.

ta! :)

>    end
> "FrameworkSpawner when in smart spawning mode an AbstractServer
> raises AlreadyStarted if the child process is still running"

You're right, these do read funny don't they?

I still don't think you've got far enough along the implementation-
 >behavioiur spectrum in your example names and that's partly why it
still feels weird. I'd rid of that reference to the AbstractServer
altogether, for starters.

Don't forget that the name of the shared example group does not go
into the specdoc - it's just a label you use when pulling it in with
it_should_behave_like.

Maybe you could put another ExampleGroup block inside your shared
group to describe the context?

e.g.: "FrameworkSpawner when in smart spawning mode when the child
process is still running should raise an AlreadyStarted error"

Also bear in mind that you can split your shared behaviour and have
more than one shared example group, and 'mix them in' to define the
appropriate behaviour in different contexts.
Dan N. (Guest)
on 2008-10-10 12:52
(Received via mailing list)
Another approach would be to prefer composition over
inheritance<http://www.google.com/search?q=prefer%20compositio...,
and either inject the server in a constructor (with a suitable name for
the
class) or mix it in as a module.

Then you could describe the common server behaviour in one set of
examples,
and just have a couple of mock-based examples to verify that the
"subclasses" interact correctly with the Server.

Cheers,
Dan


2008/10/10 Matt W. <removed_email_address@domain.invalid>
This topic is locked and can not be replied to.