|
Boost Users : |
From: Rush Manbert (rush_at_[hidden])
Date: 2006-09-14 14:01:59
Scott Meyers wrote:
> Rush Manbert wrote:
>
>>In a case like the example, where I know it might be hard to setup the
>>object correctly, I'd rather have a constructor that took some bogus
>>argument type that set the object up in "simulated success" mode, but
>>only in my debug build.
>
>
> I'm glad you mentioned this, because my recent knowledge of unit testing
> comes from books like Beck's "Test-Driven Development" and Feathers'
> "Working Effectively with Legacy Code" as well as countless breathless
> articles lauding the wonder of unit testing and showing how it's all
> oh-so-simple.
LOL - "oh-so-simple" breaks down pretty quickly in any large system,
doesn't it?
My feeling is that it would often be convenient to have a
> special "test" interface, but I've never seen any discussion of doing
> that. I can imagine a couple of ways of doing this, one being to
> literally have a different interface for debug builds, another to have
> "test only" interface elements cordoned off somewhere by convention
> (similar to Boost's use of namespaces named "detail").
I have done this in the past. Here's a real world example:
The application was for a storage virtualization controller. There were
two separate processors running different software. One handled the
virtualization and client services, while the other actually knew how to
do I/O to the storage devices. My subsystem was a service that did
copies of various flavors (entire logical units, etc.). Since it
couldn't actually do any I/O, it could send messages to the other
processor that created, started, stopped, etc. a copy utility task. In
the real case, the utility would send events to my service to let it
know how many blocks had been copied, if an error occurred, etc. There
could be many instances of copy processes/utilities running concurrently
and independently.
I had to test the top level service without any support from the copy
utility task on the other processor. (You may wonder why. Let's just say
that there were two separate groups that developed software for the
different processors, and we had somewhat different ideas regarding
testability. My group also had a version of our code that ran on Windows
and I needed to be able to test it there.) This meant that I needed to
simulate the event stream from the utility task. I also had to be able
to force errors (again by simulating events).
Needless to say, this required a fairly extensive testing API, plus a
notion within the objects that implemented the service that it could be
running in "test mode". I think it took as much or more work to develop
the test interface and the test drivers as it took to develop the
service itself, but it was completely worth it. Especially when there
was a problem and we needed to sort out whether it was "our" code or
"their" code. ;-) Also, since I had this capability, the Windows version
of the service could be driven by our management UI, and the copy
processes would appear to make progress, stop, start, and complete. The
management UI couldn't tell that they were "fake", so that group could
use it to test their software. It was really cool, but the reasons that
it was even possible was that a testing subsystem was built into our
code, and we were required to have complete tests for our subsystems,
and I had to consider how to test my subsystem from day one.
In fact, our system shipped with the test subsystem included. It was not
readily accessible, of course, but was really useful in some cases where
we needed to test something on an installed system. This sort of
capability in the field can be a real saving grace in an embedded system.
>
>
>>EventLog::EventLog (bool dummyarg)
>>{ // This constructor sets us up in simulated success mode.
>> #ifdef DEBUG // or whatever you use
>> m_simulateSuccess = true;
>> #else
>> throw something useful
>> #endif
>>}
>
>
> Hmmm, I'd think this entire constructor would exist only in debug
> builds, e.g.,
>
> class EventLog {
> public:
> #ifdef DEBUG
> EventLog(bool dummyarg);
> #endif
>
> Unfortunately, conditional compilation is not without its own resultant
> spaghetti code and concomitant maintenance headaches.
>
Oh, sure. Get the error at compile time if you're going to go this way.
The conditional compilation stuff is a definite problem to be avoided if
possible. It's hard to avoid if you want to implement a really
comprehensive test interface, but does seem less desirable in order to
support "exploratory" programming (which I think was the original
context here).
- Rush
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