Forwarded message from Chuck Remes:

Begin forwarded message:

From: Chuck Remes <[EMAIL PROTECTED]>
Date: June 13, 2008 10:31:12 AM CDT
To: rspec-users <[email protected]>
Subject: Re: [rspec-users] help with test design


On Jun 11, 2008, at 2:25 PM, David Chelimsky wrote:

On Jun 11, 2008, at 12:06 PM, Chuck Remes wrote:

Ideally, all of these steps would be private to the Adapter class and would expose a single public interface called #build. All of these public methods exist solely so I can test these specific behaviors.

Any suggestions on how to accomplish my goals? Is my difficulty pointing at a code smell that I am not detecting?

Hey Chuck - sounds like you're trying to plan the whole process out up front. TDD is about discovery. Here's what I'd recommend:

Start w/ a simple example of what you want the adapter to do - the simplest think you can think of - when you send it the #generate_internal_instruments message. Get that to pass. Now the next simplest thing. Get that to pass. Refactor. Rinse. Repeat.


[I apologize if this shows up multiple times. I sent it last night and again this morning but it never showed up on the list or in the archives. I subsequently unsubscribed and resubscribed. <crossing fingers 3rd time is the charm>]


Warning! Long post ahead! It responds to both Pat & David.

Pat, no problem. Let me take another stab at this. I'll break things out into their own sections. I've actually made some progress since I sent the email so I'll incorporate those changes in my thinking.

Oh, and to answer David's response that I am doing the whole process up front, the code I have so far is a direct result of tests. I *did* think through the problem beforehand a little bit because I had to pick where to start.

Lastly, I have put the code for the class at the end of this email. The tests are sprinkled throughout but the class code is in one spot.

GOALS

1. Practice BDD so no business-logic code is written without a test being written first.

2. Create an Adapter class to act as a proxy/adapter between a 3rd Party Library and my business logic

2a. Adapter class may use additional helper classes to convert the data inside objects from the 3rd Party lib into ruby objects used by my business logic

3. End up with well-tested code that only exposes as many public methods as necessary to accomplish the aforementioned goals

CODE GOALS

1. To insulate my business logic from this 3rd party lib, I need to load and copy a bunch of things. I'm focusing on only one at a time so each "thing" will potentially get its own class unless I can refactor the load behavior out to a module or something else.

2. I need to do the following sequence of steps to load and copy the data.
        a. Get the Orc::Instruments from the 3rd party library
        b. Save the Orc::Instruments
c. Build my internal shadow copy of the Orc::Instrument which I am calling Instrument in the ruby namespace
        d. Subscribe to market data updates for each Orc::Instrument
        e. Push all market data updates from Orc::Instrument to Instrument


MY EFFORTS SO FAR

I created a class called LiquidatorInstrumentAdapter. I skipped the tests to verify that it exists and other basics. The first test I wrote was:

describe LiquidatorInstrumentAdapter, "discover_instruments" do
before(:each) do
@ia = LiquidatorInstrumentAdapter.new
@orc = mock("orc")
end

it "should accept an orc strategy object" do
@ia.orc_strategy.should be_nil
@ia.orc_strategy = @orc
@ia.orc_strategy.should_not be_nil
end
end

In order to fetch the Orc::Instruments, I need to get a reference to them via a handle I call #orc_strategy. I created an accessor on the class so I could set the #orc_strategy field. Test went from failing to passing. I could have also passed this in the constructor but I prefer the freedom of creating empty objects and filling them out after the fact. Also, I could have skipped this test altogether since I'm really just testing that #attr_accessor works. So what... it helped prime the pump.

Now that I have my reference to the orc strategy, I can get the Orc::Instruments loaded up. So I wrote this test (I created a new mock in my before block too):

it "should get all instruments from Orc" do
@orc_instruments.should_receive(:nil?).and_return(false)
@orc_instruments .should_receive(:getKind).and_return(Orc::InstrumentKind::FUTURE) @orc .should_receive (:getInstrumentParameter ).with("instrument").and_return(@orc_instruments)
@ia.orc_strategy = @orc
@ia.discover_instruments
end

The logic here is that I should be able to call #orc_strategy.getInstrumentParameter and get an Orc::Instrument object for my efforts. I also make sure to do a sanity check for a nil instrument. Please note that in order for me to inject this orc_instrument mock that I need to expose another accessor (called #orc_instruments). This is one of the details that upsets me about testing, but I'll get to that at the end.

This nil check lead me to my next test.

it "should raise an exception if the instrument is nil" do
@orc.should_receive(:getInstrumentParameter).and_return(nil)
@ia.orc_strategy = @orc
lambda { @ia.discover_instruments }.should raise_error
end

No surprises here; sometimes the 3rd party lib fails to return a valid object so I need to check for that. To save space, I'll skip the last test I wrote which was verifying that the Orc::Instrument it receives back is of type COMBINATION or FUTURE since those are the only instrument kinds I want to handle right now. I moved on to the next step in my logical sequence which was to save these Orc::Instruments for future reference. So I wrote another test:

describe LiquidatorInstrumentAdapter, "save_components" do
# skipping the #before block which sets up some mocks
it "should save the component when the instrument is a FUTURE" do
@ia .orc_instrument .should_receive(:getKind).and_return(Orc::InstrumentKind::FUTURE)
@ia.orc_instrument.should_receive(:getInstrument).and_return(1)
@ia.orc_strategy.should_receive(:setParameter).with("components", @ia.orc_components)
@ia.orc_components.should be_empty
@ia.save_components
@ia.orc_components.length.should == 1
end
end

This test verifies that the Orc::Instrument is the proper kind and that my internal array called #orc_components is empty at the beginning and contains one element after sending the message. Note again that I had to expose what should likely be a private structure in order to test it (attr_accessor :orc_components). I wrote a few more tests to make sure it properly saves COMBINATIONS (which contain multiple FUTURES) and that they are saved off correctly. I also wrote a test to verify an exception is raised if the instrument is not a COMBINATION a FUTURE (this is probably unnecessary since it was validated in the prior method).

At this point I have a class starting to take shape. Moving on to the next step takes me to building my shadow copy of these Orc::Instrument objects as ruby objects. I didn't know what this interface should look like so I started out by using mocks and letting it lead me. After a little experimentation it was clear that I needed to break this functionality out to its own class (lots of copying fields from one object to another). I elected to call it OrcInstrumentCreationEvent. Here's the test I wrote for it prior to actually creating that class for real (all mocks).

describe LiquidatorInstrumentAdapter, "generate_instrument_creation_events" do
before(:each) do
@ia = LiquidatorInstrumentAdapter.new
end

it "should create a InstrumentCreationEvent for each component" do
orc_components = mock("components")
event = mock("instrument event class")
event .should_receive (:new).exactly(:twice).with(orc_components).and_return(event) orc_components .should_receive (:each).and_yield(orc_components).and_yield(orc_components)

@ia.orc_components = orc_components
ary = @ia.generate_instrument_creation_events(event)
ary.size.should == 2
end
end

So this verifies that I create a CreationEvent for each Orc::Instrument that I saved earlier. All the gory details of copying the right fields from the Orc::Instrument will be encapsulated by that class. Note that I need to pass a Class as a parameter to #generate_instrument_creation_events so that my test can pass it a mock and verify the behavior. In the real world I'll pass a ruby Instrument but ideally (in my mind) this wouldn't be a parameter at all. It looks like I am contorting my code so that it is testable.

I still haven't created my shadow ruby Instrument object yet. Hmmm... let's write a test.

describe LiquidatorInstrumentAdapter, "generate_internal_instruments" do
before(:each) do
@ia = LiquidatorInstrumentAdapter.new
end

it "should create a new Instrument for each event and #build it" do
event_ary = mock("instrument creation event array")
instrument_class = mock("instrument class")
empty_mock = mock("placeholder")
event_ary.should_receive(:each).and_yield(empty_mock)
instrument_class.should_receive(:new).and_return(instrument_class) # reuse this mock
instrument_class.should_receive(:build).with(empty_mock)
@ia.generate_internal_instruments(event_ary, instrument_class)
end
end

Taking my CreationEvents, I can pass these to the Instrument#build method and it will know which accessors to use to pull the appropriate data out. These are all mocks. I'll create the real objects a little bit later (not covered in this email). Now I'm scratching my head wondering if I know what the hell I'm doing. To test this I have to pass in both an event array and the class name of the object I want to instantiate and #build. This is all in the name of testability so I can inject my mocks into the stream and verify the behavior. But it looks fugly to me. Ideally the last two methods I wrote would be private to the class and never exposed to the outside world. But one of the cardinal rules of testing (from what I've read) is that we should *not* test private methods. Is this a circumstance where that rule doesn't apply?

Anyway, the class code is relatively short. I have about double the lines in my spec file to test the class. That doesn't bother me too much but there are a few things bugging me which I'll list.

THINGS THAT ARE BUGGING ME

1. To use mocks to verify behavior, I have to create many public accessors to inject the mocks. These public accessors, in most cases, would not be part of any public API that I would publish for another programmer to use. Ideally they would set a few things (like orc_strategy) and then call #build on the whole object which would privately call most of the methods I have defined as public.

2. I'm doing lots of mock setup in each test. My rule of thumb has been to break a method out to another (public) method if I have to setup more than 3 mocks to test its behavior. I don't know if this is pointing to a larger problem (lots of setup) or what, but I would be grateful for any insight.

3. I think some of my mocks are exposing too much of the class' implementation. For example, in my OrcInstrumentCreationEvent test I tell the orc_components mock to expect the message :each which is exposing how I am iterating. What if I want to use a while or a for loop? That shouldn't be any business of the test. It cares about behavior, not implementation but I don't see how else to get the mock to return what I need.

I would love to have this critiqued by veteran BDDers. Be as blunt as necessary... I have broad shoulders.

cr

CLASS CODE

module Orc
import com.orcsoftware.liquidator.InstrumentKind
end

class LiquidatorInstrumentAdapter

attr_accessor :orc_strategy
attr_accessor :orc_components
attr_accessor :orc_instrument
attr_accessor :instruments

def initialize
@orc_components ||= []
@instruments ||= []
end

def build
discover_instruments
save_components
events = generate_instrument_creation_events (LiquidatorInstrumentCreationEvent)
generate_internal_instruments(events, Instrument)
subscribe_to_components
end

def discover_instruments
@orc_instrument = orc_strategy.getInstrumentParameter("instrument")
raise TypeError, "Instrument parameter was nil!", caller[0] if orc_instrument.nil?
kind = orc_instrument.getKind
if (kind != Orc::InstrumentKind::COMBINATION && kind != Orc::InstrumentKind::FUTURE) raise TypeError, "Instrument parameter was the wrong type [#{kind}], should be either [#{Orc::InstrumentKind::COMBINATION}] or [#{Orc::InstrumentKind::FUTURE}]"
end
end

def save_components
case orc_instrument.getKind
when Orc::InstrumentKind::COMBINATION
  save_combination_components
when Orc::InstrumentKind::FUTURE
  save_future_component
else
raise TypeError, "Instrument is the wrong kind! : [#{orc_instrument.getKind}]", caller[0]
end
orc_strategy.setParameter("components", orc_components)
end

def subscribe_to_components
orc_components.each { |component| component.depthSubscribe }
end

def generate_instrument_creation_events(klass)
events = []
orc_components.each do |component|
  events << klass.new(component)
end
events
end

def generate_internal_instruments(event_ary, klass)
event_ary.each do |event|
  instruments << klass.new
  build_strategy_instrument(instruments.last, event)
end
end

private

def save_combination_components
orc_instrument.getComponents.each do |component|
  orc_components << component.getInstrument
end
end

def save_future_component
orc_components << orc_instrument.getInstrument
end

def build_strategy_instrument(instrument, creation_event)
instrument.build(creation_event)
end
end



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

Reply via email to