Forum: Ruby Need help with TDD, stubs and mocks

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.
unknown (Guest)
on 2006-06-05 19:16
(Received via mailing list)
I'm writing a script to process a directory structure and some files
within it. The process depth-first searches the directory tree from
some root and looks for a Manifest file. When it finds it, it
processes the file (XML plist, containing keys for filenames and
paths) and records some information about the files referenced in the
manifest. This recorded information then gets posted to a web service
via SOAP.

Actually, I already wrote this using my usual top-down style. First,
I wrote the code to depth-first search the directories. This included
code to test all entries in a directory (using #file? and
#directory?). Next I wrote the code to detect the manifest file. Then
I used the plist gem to read it into memory (great gem). Then I wrote
the code to do my magic on the files referenced in the plist. Etc.

I just iterated through my requirements until I had a finished product.

When I finished I decided to write tests to verify each method.
However, the main entry point into the script starts off by doing the
directory traversal which then feeds into every other step. I can't
call the method without running all of its dependent methods (which
essentially runs the whole program). This strikes me as my first clue
that I probably did this wrong.

So now I'd like to rewrite it using TDD.

I need some advice from the TDD experts out there.

1. Is it fair to say that I should rewrite bottom-up? That is, start
with methods that are independent of others. For example, assume no
subdirs and just get a list of entries from the current directory.
Then write the method to iterate those entries. Then write one to
test if the entry is a file or a directory. Etc.

2. Do you generally keep all methods in a class independent of each
other? How do you write, and then test, methods that have
dependencies on each other (and therefore cause side effects)?

3. How does one mock or stub out a SOAP call? What's the difference
between a stub and a mock?

4. How do you test a method that utilizes an instance variable that
was set somewhere else? E.g. #methodA calculates @result. #methodB
retrieves @second_result. #methodC compares @result and @second_result.

Instead of treating these as global to the class instance, should I
be passing them as method parameters?


I appreciate any and all insight. I'm hoping to continually improve
as a programmer so I can *answer* these questions instead of always
asking them. :-)

cr
Jim W. (Guest)
on 2006-06-05 19:56
cr wrote:
> When I finished I decided to write tests to verify each method.
> However, the main entry point into the script starts off by doing the
> directory traversal which then feeds into every other step. I can't
> call the method without running all of its dependent methods (which
> essentially runs the whole program). This strikes me as my first clue
> that I probably did this wrong.

Code not done test first will often be hard to test.  I think we just
discovered that.

> 1. Is it fair to say that I should rewrite bottom-up? That is, start
> with methods that are independent of others.

When I do TDD, I start with very simple cases and work toward more
general cases.  Both bottom-up and top-down work, I tend to do
outside-in (i.e. start at both the top and bottom and work toward the
middle).

> 2. Do you generally keep all methods in a class independent of each
> other? How do you write, and then test, methods that have
> dependencies on each other (and therefore cause side effects)?

Dependencies?  Like "You Must Call Method A before Method B"?  I try to
avoid those kinds of dependencies when possible.  Sometimes the
semantics of the object naturally introduce them (e.g. you usually have
to push something in order to pop something from a stack).


> 3. How does one mock or stub out a SOAP call? What's the difference
> between a stub and a mock?

Is there an object that you use to perform the soap call?   If so, just
replace the object with a Stub or Mock.

Stub VS Mock:  (Terminalogy varies, but the following is a common
distinction) Mocks check their calls for correctness, stubs don't.
Stubs are used to provide external behavior that might not exist in a
test environment.  Mocks are used to verify that you are actually
calling particular methods with paricular arguments.

Note that using this terminology allows a single object to mock some
methods and stub others.  I do this alot.  If you divide methods into
two camps: those that return values (queries) and those that do things
(commands), I usually stub queries and mock commands.

> 4. How do you test a method that utilizes an instance variable that
> was set somewhere else? E.g. #methodA calculates @result. #methodB
> retrieves @second_result. #methodC compares @result and @second_result.

Construct your test case so that methodA and methodB are called before
methodC does its work.

Example:

   def test_comparison
     thing = Thing.new
     thing.methodA
     thing.methodB
     assert_equal ExpectedResult, thing.methodC
   end


-- Jim W.
unknown (Guest)
on 2006-06-05 21:00
(Received via mailing list)
On Jun 5, 2006, at 10:57 AM, Jim W. wrote:

>
>    def test_comparison
>      thing = Thing.new
>      thing.methodA
>      thing.methodB
>      assert_equal ExpectedResult, thing.methodC
>    end

Jim,

thanks for your response. I do need some additional clarification on
this final point. The way I see your answer, you are NOT suggesting I
pass instance variables as parameters. Instead, I should enforce the
order of operations for my methods and allow them "global" (in this
class instance) access to the instance variables.

I still see this as somewhat problematic if there is a long chain of
"custody" in between where the instance variable gets set and where
it gets used again by a later method. I'll have to think on this a
bit more.

BTW, I really appreciated the distinctions you made on mocks versus
stubs. I think I get it now. Now all I need to do is repeat this a
thousand times to imprint it on my brain.

Time to test!

cr
Jim W. (Guest)
on 2006-06-06 01:07
unknown wrote:
> On Jun 5, 2006, at 10:57 AM, Jim W. wrote:
>
>>
>>    def test_comparison
>>      thing = Thing.new
>>      thing.methodA
>>      thing.methodB
>>      assert_equal ExpectedResult, thing.methodC
>>    end
>
> Jim,
>
> thanks for your response. I do need some additional clarification on
> this final point. The way I see your answer, you are NOT suggesting I
> pass instance variables as parameters.

I don't have enough information to make a recommendation one way or
another.  As a general rule of thumb, I would try to avoid order
dependencies in methods, but if you have them, its not an inordinate
burden for testing.

> Instead, I should enforce the
> order of operations for my methods and allow them "global" (in this
> class instance) access to the instance variables.

Your use of "global" is confusing to me.  To you mean access to the
variable outside of the object?

Without further details, all one can do is make general statements.  My
recommendation:  determine the responsibilities of the object and make
sure the methods for that object are adequate to allow it to perform
those responsibilities.

-- Jim W.
This topic is locked and can not be replied to.