Specing protected methods


#1

Hi everyone,
Do anyone know if there’s a way to spec protected methods ?
Thanks in advance

– Bastien


#2

On 23 Oct 2008, at 10:16, Bastien wrote:

Hi everyone,
Do anyone know if there’s a way to spec protected methods ?
Thanks in advance

Sorry if this sounds preachy, but I would always advise you to factor
out the code in the method into another class, then test that class.

If the behaviour of the method is so complex you need to test it on
it’s own, it deserves to be in a class all by itself.

This is what we call, ‘listening to your specs’ - if it’s hard to
spec, you’ve probably got the design wrong.

HTH,
Matt


#3

I totally agree with the ''listening to your specs" concepts and
always divide my code into small methods easier to spec.
Now let me get you right, here’s my code :

class Survey < ActiveRecord::Base
has_many :participants

def generate_reports

sub_total = sub_total(participants.reports)

end

protected

def sub_total(reports)
sub_total = 0
reports.collect do |report|
sub_total = sub_total + report.score
end
return sub_total
end

end

I m already specing the “generate_reports” methods with a mock on
“sub_total”, but I would also like to spec that method. I’ve put this
method into protected because it doesn’t make sens to call it from
outside this class. But you’re arguing that I should put that method
into a lib ? I m not sure it would make sense as this method is very
specific to that class, and would actually be easy to spec. Except
that it’s not possible to call a protected method from a spec as far
as I know, correct me if I m wrong. (I maybe didn’t explain my problem
well in the first place)

Regards

– Bastien


#4

Bastien removed_email_address@domain.invalid writes:

...

end

end

I m already specing the “generate_reports” methods with a mock on
“sub_total”

Why are you mocking #sub_total?

Pat


#5

On Thu, Oct 23, 2008 at 6:49 AM, Bastien removed_email_address@domain.invalid
wrote:


end
as I know, correct me if I m wrong. (I maybe didn’t explain my problem
well in the first place)

This sort of decision is always a balancing act. Leaving it where it
is means you have to either

  • unprotect it, which you clearly prefer not to do
  • expose it for you code example using clever ruby trickery
  • give up testing it directly
  • move it to another class, adding conceptual weight to your system

None of these are perfect and all come with some tradeoff.

I’ll confess that, in practice, I’d probably not test this directly.
More often than not, for me, the sub_total method is a result of
refactoring the generate_reports method, which by that time would have
at least two code examples in which the only difference is the sum of
scores of the reports.

In this case, however, assuming participants and reports are AR backed
models, you don’t really need to introduce a new class:

subtotal = participants.reports.sum(‘score’)

Cheers,
David


#6

Thanks for all your answers.

I was just wondering how about using send in my specs ? It would
bypass the protected limitation without altering my code :

@survey.send( :sub_total, [@mock_report] )

Is that the kind of trickery you were referring to David ? Would that
be bad practice ?

Regards

– Bastien


#7

On Thu, Oct 23, 2008 at 7:49 AM, Bastien removed_email_address@domain.invalid
wrote:

I m already specing the “generate_reports” methods with a mock on
“sub_total”, but I would also like to spec that method. I’ve put this
method into protected because it doesn’t make sens to call it from
outside this class. But you’re arguing that I should put that method
into a lib ? I m not sure it would make sense as this method is very
specific to that class, and would actually be easy to spec. Except
that it’s not possible to call a protected method from a spec as far
as I know, correct me if I m wrong. (I maybe didn’t explain my problem
well in the first place)

If you’re somehow mocking the sub_total method while specing the
generate_reports method, don’t do that. Don’t stub/mock the thing that
you’re specing, only stub/mock its collaborators, i.e., its
dependencies. I
would either verify via generate_reports that the sub totals on the
report
are correct, or I’d factor out something like a ReportGenerator class
and
spec it. Then, I’d stub/mock out the ReportGenerator in the specs for
the
Survey class.

Regards,
Craig


#8

On Thu, Oct 23, 2008 at 10:00 AM, Bastien removed_email_address@domain.invalid
wrote:

Thanks for all your answers.

I was just wondering how about using send in my specs ? It would
bypass the protected limitation without altering my code :

@survey.send( :sub_total, [@mock_report] )

Is that the kind of trickery you were referring to David ? Would that
be bad practice ?

If you’re going to do this, use send or your code examples will
break if/when you go to Ruby >= 1.9.

As for good/bad practice, here’s the way I see it: there is a lot of
evidence that encapsulation is a good thing in that it constrains
contact points between objects, making them less coupled and easier to
modify internally, swap in and out (polymorphism), etc, etc.

Every time you violate encapsulation by using send, even in code
examples, you increase coupling by a little bit, and make your system
that much more brittle. So while one violation might seem harmless,
it’s a slippery slope (sorry for the cliché).

With all that - you’ll find send in my code, and in my code
examples. But I’m conscious of the risks. FWIW.

Cheers,
David


#9

It appears we were too clever, but I think this would be a reasonable
use of stories.

We tried to have a story file run with 2 different sets of steps to
use the same story against both the UI using selenium and against the
server API using regular get/put/post. This way we could spec the
server API and the UI while ensuring they are in sync for the scenario
in question. But stories get confused when there are stories or
scenarios with the same names.

Does Cucumber deal with this any better than the story runner? Given
that it searches for steps rather than having them declared, I would
think you can not share story files for different steps?

Michael


#10

On Thu, Oct 23, 2008 at 1:20 PM, Michael L. removed_email_address@domain.invalid wrote:

Does Cucumber deal with this any better than the story runner? Given that
it searches for steps rather than having them declared, I would think you
can not share story files for different steps?

You can do this, you’d just put the steps in different directories and
tell cucumber to require the files in one directory for one run, and
another for another run. ie:

cucumber --require features/steps/selenium/ features/
AND
cucumber --require features/steps/normal/ features/


Zach D.
http://www.continuousthinking.com
http://www.mutuallyhuman.com


#11

On Thu, Oct 23, 2008 at 5:16 AM, Bastien removed_email_address@domain.invalid
wrote:

Hi everyone,
Do anyone know if there’s a way to spec protected methods ?
Thanks in advance

FWIW, I blogged my answer to this question recently:
http://avdi.org/devblog/2008/10/21/testing-private-methods/


Avdi

Home: http://avdi.org
Developer Blog: http://avdi.org/devblog/
Twitter: http://twitter.com/avdi
Journal: http://avdi.livejournal.com


#12

On Thu, Oct 23, 2008 at 5:00 PM, Avdi G. removed_email_address@domain.invalid wrote:

FWIW, I blogged my answer to this question recently:
http://avdi.org/devblog/2008/10/21/testing-private-methods/

…and to be a little more concrete, at first glance the way I’d apply
that advice to your code example is to make a class that either
subclasses or wraps (via DelegateClass/SimpleDelegate) Array and adds
a #sub_total method, something like the following:

class Survey < ActiveRecord::Base
has_many :participants

def generate_reports

sub_total = ReportCollection.new(participants.reports).sub_total

end

class ReportCollection < SimpleDelegator
def sub_total
sub_total = 0
self.collect do |report|
sub_total = sub_total + report.score
end
return sub_total
end
end

end


Avdi

Home: http://avdi.org
Developer Blog: http://avdi.org/devblog/
Twitter: http://twitter.com/avdi
Journal: http://avdi.livejournal.com


#13

That works when running one at a time. When I want to run all my
specs how do I ensure the requires are applied to the appropriate
features? It is not all my stories, only a subset that are dual-
purposed. Do I need to have directories for those that can be run
dual mode and those that are not, and then have a script that runs the
3 cases?

Michael


#14

On Thu, Oct 23, 2008 at 11:20 PM, Michael L. removed_email_address@domain.invalid wrote:

That works when running one at a time. When I want to run all my specs how
do I ensure the requires are applied to the appropriate features? It is not
all my stories, only a subset that are dual-purposed. Do I need to have
directories for those that can be run dual mode and those that are not, and
then have a script that runs the 3 cases?

You have to run once for each “environment”. So if you have say
selenium and rails, you’ll have to run all features that use selenium
in one run and those that use rails in another. (Of course you can
have overlap - some features that include run in both runs)

Aslak


#15

On Thu, Oct 23, 2008 at 7:20 PM, Michael L. removed_email_address@domain.invalid wrote:

Does Cucumber deal with this any better than the story runner? Given that
it searches for steps rather than having them declared, I would think you
can not share story files for different steps?

I’ve done this on a couple of projects.

steps/common/_steps.rb
steps/watir/
_steps.rb
steps/rails/*_steps.rb

Set up two tasks and use --require. Or use --profile.

See
http://github.com/aslakhellesoy/cucumber/wikis/migration-from-rspec-stories
for details.

Aslak