Hongli Lai
2008-Oct-09 17:20 UTC
[rspec-users] What''s the best way to test inherited behavior?
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 Chelimsky
2008-Oct-09 18:11 UTC
[rspec-users] What''s the best way to test inherited behavior?
On Thu, Oct 9, 2008 at 12:20 PM, Hongli Lai <hongli at plan99.net> 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. > > 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.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 Wynne
2008-Oct-09 18:37 UTC
[rspec-users] What''s the best way to test inherited behavior?
On 9 Oct 2008, at 18:20, Hongli Lai 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 Lai
2008-Oct-09 19:07 UTC
[rspec-users] What''s the best way to test inherited behavior?
David Chelimsky wrote:> Not sure why what you''re doing works at allOh oops, I forgot to mention that I also have ''shared_examples_for "AbstractServer-like" behavior''.
Pat Maddox
2008-Oct-09 19:18 UTC
[rspec-users] What''s the best way to test inherited behavior?
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-with-shared-example-groups
Hongli Lai
2008-Oct-09 19:35 UTC
[rspec-users] What''s the best way to test inherited behavior?
Pat Maddox 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 Wynne
2008-Oct-10 07:53 UTC
[rspec-users] What''s the best way to test inherited behavior?
On 9 Oct 2008, at 20:35, Hongli Lai wrote:> Pat Maddox 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! :)> 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"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.> 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. > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Dan North
2008-Oct-10 08:48 UTC
[rspec-users] What''s the best way to test inherited behavior?
Another approach would be to prefer composition over inheritance<http://www.google.com/search?q=prefer%20composition%20over%20inheritance>, 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 Wynne <matt at mattwynne.net>> On 9 Oct 2008, at 20:35, Hongli Lai wrote: > > Pat Maddox 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! :) > > 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" >> > > 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. > > > > 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. >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >> > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://rubyforge.org/pipermail/rspec-users/attachments/20081010/94ed57cd/attachment.html>