From: ian-news@this.is.invalid   
      
   Richard wrote:   
   > Ian Collins spake the secret code   
   > thusly:   
   >   
   >> [...] For unit testing, you don't want to bring in all the real   
   >> dependencies of the code under test, you want to be able to monitor   
   >> and control the interfaces your unit is using. TO do that, you mock   
   >> them.   
   >   
   > Yes, I agree completely. The difficulty of Singletons isn't that they   
   > are a singleton per se, but that the SUT has lots of code like this:   
   >   
   > class Single   
   > {   
   > public:   
   > static Single *instance();   
   > // other public methods but Single is not a pure virtual base   
   > void doSomething();   
   > void doOtherThing();   
   >   
   > private:   
   > Single();   
   > ~Single();   
   > };   
   >   
   > class SUT   
   > {   
   > public:   
   > void someMethod();   
   > // ...   
   > };   
   >   
   > void SUT::someMethod()   
   > {   
   > Single::instance()->doSomething();   
   > // ...   
   > Single::instance()->doOtherThing();   
   > // ...   
   > }   
   >   
   > ....which means I'm left only with link-time substitution as a way to   
   > mock out the singleton if I leave this code unchanged.   
      
   Which isn't a problem if (like me) link-time substitution or   
   interposition is part of your standard test process.   
      
   > Consider this minor refactoring, however:   
   >   
   > class Single   
   > {   
   > public:   
   > virtual ~Single() { }   
   >   
   > // pure virtual base defining interface to Single   
   > virtual void doSomething() = 0;   
   > virtual void doOtherThing() = 0;   
   > };   
   >   
   > class ProductionSingle : public Single   
   > {   
   > public:   
   > static Single *instance();   
   >   
   > private:   
   > ProductionSingle();   
   > virtual ~ProductionSingle();   
   > };   
   >   
   > class SUT   
   > {   
   > public:   
   > void someMethod() { someMethod(ProductionSingle::instance()); }   
   > void someMethod(Single *single);   
   > };   
   >   
   > void SUT::someMethod(Single *single)   
   > {   
   > single->doSomething();   
   > // ...   
   > single->doOtherThing();   
   > // ...   
   > }   
   >   
   > Now I can test SUT::someMethod without it being directly coupled to   
   > the production singleton; in fact, there's NOTHING in SUT that   
   > requires it's collaborator to be a singleton, but the choice of making   
   > that collaborator a singleton "leaked" into the implementation of SUT   
   > making it harder to test.   
      
   But haven't you added a new method to SUT just to avoid the coupling?   
      
   > Extracting an interface over Single and   
   > using DI (at the method level) makes testing SUT::someMethod *much*   
   > easier and I don't have to resort to link-time substitution in order   
   > to test the method. Existing code that calls SUT::someMethod()   
   > doesn't have to change.   
      
   Just about everything in software development is a trade off. In your   
   case you have added code to SUT to facilitate testing, in mine I would   
   add a description of Single to the file used to generate mocks. Using   
   abstract classes to support testing is a perfectly valid approach, but   
   you hand code a test derived class where a mock framework will do the   
   code generation for you.   
      
   For example, given your code if you wanted to test SUT::someMethod   
   called doSomething(), you might have to write something like:   
      
   struct TestSingle : Single   
   {   
    bool doSomethingCalled;   
    bool doOtherThingCalled;   
      
    TestSingle() : doSomethingCalled(), doOtherThingCalled() {}   
      
    void doSomething() { doSomethingCalled = true; }   
    void doOtherThing() { doOtherThingCalled = true; }   
   }   
      
   void testSomeMethodCallsDoSomething()   
   {   
    TestSingle single;   
      
    SUT sut;   
      
    sut.someMethod( &single );   
      
    TEST_ASSERT( single.doSomethingCalled );   
   }   
      
   Which isn't too bad for one simple class, but can get messy with more   
   member functions and if there are parameter values to check.   
      
   With the harness I use, I would add an XML description of Single:   
      
       
       
       
       
       
      
   then my test would be   
      
   void testSomeMethodCallsDoSomething()   
   {   
    SUT sut;   
      
    sut.someMethod( &single );   
      
    TEST_ASSERT( Single::doSomething::Called );   
   }   
      
   --   
   Ian Collins   
      
      
    [ See http://www.gotw.ca/resources/clcm.htm for info about ]   
    [ comp.lang.c++.moderated. First time posters: Do this! ]   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   
|