|
Boost Users : |
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
Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net