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