$include_dir="/home/hyper-archives/boost-users/include"; include("$include_dir/msg-header.inc") ?>
From: Joel de Guzman (joel_at_[hidden])
Date: 2006-09-14 04:21:24
Scott Meyers wrote:
> Joel de Guzman wrote:
>> One-phase construction definitely! Testing in isolation is a different
>> matter and should not degrade the interfaces. There are ways to allow
>> isolated testing without degrading the interface. It all boils down
>> to decoupling and isolating dependencies. My favorites:
>>
>> 1) Use a template where policies can be replaced by hooks to the
>>     testing engine that tests the expected results. In your example,
>>     I'd imagine this interface: template <class Printer> EventLog.
> 
> Or the more conventional OO approach of subclassing from ostream (in the 
> example) and passing in a null or mock derived class object for testing 
> purposes.
> 
> Your approach has the drawback that it's now more difficult to have a 
> container of all EventLog objects (because Printer is part of the type) 
> and the OO approach has the drawback of requiring the introduction of a 
> base class and virtuals in cases where they might otherwise not be 
> necessary.  To modify the example, suppose the EventLog constructor 
> requires a Widget, and Widget is a large nonpolymorphic object with no 
> virtuals.  I'd still pass the Widget by reference, but subclassing it 
> for testing would be ineffective, due to the lack of virtuals.
> 
> Either way the desire to make the class testable affects the interface 
> seen by users.  This is not a complaint, just an observation.  In 
> another post, I noted that it seems like it'd be nice to be able to 
> somehow create a "testing only" interface separate from the "normal" 
> client interface.
> 
>> 2) Use callbacks. In the example you provided, I'd imagine EventLog
>>     calls logstream to print. So, I'd use a constructor like:
>>     EventLog::EventLog(boost::function<void(std::string const&)> print)
>>     instead. So, instead of calling logstream << stuff directly,
>>     I'll call print(stuff). For the testing engine, I'll replace it
>>     with something that tests the expected results.
>>
>> All these falls under the "Hollywood Principle: Don't call us,
>> we'll call you". IMO, with proper design, you can have both single
>> phase construction *and* isolation testing.
> 
> But can you also have maximal inlining and, where needed by clients, 
> runtime polymorphism?  Templates preserve inlining but tend to sacrifice 
> polymorphism (e.g., it's hard to have a container of (smart) pointers to 
> EventLog<T> objects for all possible Ts), while base class interfaces 
> and callbacks preserve polymorphism at the expense of easy inlining.
How many Ts (for all EventLog<T>) do you need? For deployment in an
application, surely the set of Ts is bounded. If there is a need to
put them in a container, I'd place them in a tuple or a fusion::set,
or if you have more than one instances of each, a tuple or a fusion::set
of std::vector(s).
Regards,
-- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net