MockCpp手册(英文)

来源:互联网 发布:图片来源搜索软件 编辑:程序博客网 时间:2024/06/05 12:39

AdvancedGuideOfMockcpp  


UserManual, Featured, Phase-Implementation

Updated Mar 16, 2010 by darwin.y...@gmail.com

  • Default Specification
  • Multiple Inheritance
  • Lifecycle Management
  • Dynamic Cast
  • Object Assignment

DefaultSpecification

Default specification is defined by using defaults(), rather than expects()/stubs(). Relations between defaults() and expects()/stubs() could be thought asrelations between default and case in C/C++ switch statements. If allspecifications of a method of a mock object specified withexpects()/stubs() don't match one of yourinvocations to the method in question, the specification defined with defaults() (if have any) will beused to perform the returned behavior.

This is a very useful feature, which helps you write yourtests in a neater way.

structTestCaseInfoReader
{
      virtualconst std::string& getCaseName()const=0;
      virtualconst std::string& getFixtureName()const=0;

      virtual~TestCaseInfoReader(){}
};

classTestGeneralTestFilter:public TESTCPP_NS::TestFixture
{
      MockObject<TestCaseInfoReader> testcase;

      GeneralTestFilter* filter;

public:

      void setUp()
      {
           filter =newGeneralTestFilter("*TestFixture","testcase?");

           MOCK_METHOD(testcase, getCaseName)
                     .defaults().will(returnValue("testcase1"));

           MOCK_METHOD(testcase, getFixtureName)
                     .defaults().will(returnValue("FooTestFixture"));
      }

     void tearDown()
     {
          testcase.reset();

           delete filter;
     }

     voidtest_should_be_able_to_recoginize_matched_fixture()
     {
          TS_ASSERT_TRUE(filter->matches(testcase));
     }

     voidtest_should_tell_unmatched_if_test_fixture_name_does_not_match()
     {
         MOCK_METHOD(fixture, getFixtureName)
            .stubs().will(returnValue("FooFixture"));

         TS_ASSERT_FALSE(filter->matches(testcase));
     }

     voidtest_should_tell_unmatched_if_testcase_name_does_not_match()
     {
         MOCK_METHOD(fixture, getCaseName)
            .stubs().will(returnValue("testcase10"));

         TS_ASSERT_FALSE(filter->matches(testcase));
     }
};

In this example, the default behaviors of methods getFixtureName() and getCaseName() return matched names.So the first test case is to test the happy path, which is pretty simple, youonly need to write one assertion to test the class TestFilter. The latter 2 test cases test for the unmatched cases,so in each test case the stubs() is used to"HIDE" the default behavior in order to do exceptional tests.

Furthermore, for one method, you can specify more thanone default specification, distinguished by the with() selectors. When fallingin the default category, mockcpp will try to select a matching defaultspecification. If multiple default specifications match your invocation, thefirst one will be selected. In the current implementation, the first matcheddefault specification defined in a test would be the one chosen by mockcpp, butthis might change in later version, so don't make your tests rely on this.Instead, you should make your default specification distinguishable.

structTestFilter
{
      virtualbool matches(TestCaseInfoReader*)const=0;

      virtual~TestFilter(){}
};

structTestCase:publicTestCaseInfoReader
{
      virtualvoid run()throw(AssertionFailure)=0;

      virtual~TestCase(){}
};

classTestTestCaseRunner: TESTCPP_NS::TestFixture
{
      MockObject<TestFilter> filter;
      MockObject<TestCase> testcase;

      TestRunner* runner;

public:
      void setUp()
      {
           runner =newSandboxTestRunner();

           MOCK_METHOD(filter, matches)
                   .defaults()
                   .with(eq((TestCase*)testcase)
                   .will(returnValue(true));

           MOCK_METHOD(filter, matches)
                   .defaults()
                   .with(neq((TestCase*)testcase)
                   .will(returnValue(false));
      }

     void tearDown()
     {
          filter.reset();
          testcase.reset();

           delete runner;
     }

     void test_should_run_matched_test_cases()
     {
          MOCK_METHOD(testcase, run).expects(once());

          runner->run(testcase, filter);

          testcase.verify();
     }

     void test_should_not_run_unmatched_test_cases()
     {
          MockObject<TestCase> unmatchedTestCase;
         
         MOCK_METHOD(unmatchedTestCase, run)
                 .expects(never());

          runner->run(unmatchedTestCase, filter);

          unmatchedTestCase.verify();
     }

     voidtest_should_throw_an_AsstionFailure_exception_if_the_test_case_did_that()
     {
          MOCK_METHOD(testcase, run)
                .stubs().will(throws(AssertionFailure("AssertionFailure"));

          TS_ASSERT_THROWS(runner->run(testcase, filter),AssertionFailure);
     }

     voidtest_should_throw_a_CrashException_if_the_test_case_crashed()
     {
          MOCK_METHOD(testcase, run)
                .stubs().will(die());

          TS_ASSERT_THROWS(runner->run(testcase, filter),CrashException);
     }
};

In this example, we used the defaults() to specify the matchedtest case and unmatched test cases, which helps us to focus on the aspectswhich we intent to test in later test cases.

Multiple Inheritance

In order to make mockcpp support multi-inheritance, youneed to make sure the configured option --disable-multi-inheritance was not specified whenyou built mockcpp. Besides, you also need to be sure the interface you aretrying to mock does not inherit from more than N other interfaces, andthe N represents"allowed maximum number of inherited interfaces" of an interface. Itwas specified with the configured optionmax_inheritance=N at the build time.Currently, the default value is 2, and maximum allowed value is 5. If yourinterface inherits from more than that, you should probably reconsider yourdesign, or you will not be able to mock it. The reason we chose this number wasnot technical, but, according to our experience, when an interface needs toinherit from many other interfaces, usually it implies a bad smell.

The similar constraint exists in the setting of allowedmaximum number of methods in one interface, which is specified with theconfigured optionmax_vtbl_size=N (N cannot be specifiedas the number more than 50, and its default value is 20). Note that the methodnumber includes all methods defined in an interface and all its parents, normalmethods and destructors. If an interface contains more than 50 (still too many,in our opinion), it's probably too fat -- having too many responsibilities.

Yes, I lied. It has technical reasons of having theseconstraints. Due to the usage of C++ template (we have tried desperately toreduce it) in our implementation, the compiling time and generated binary sizewill increase with the growth of values of these settings. If you choosemax_inheritance=5 and max_vtbl_size=50, the compiling timewould be far slower than specifying them as 2 and 20.

Non-public and virtual inheritance are not supported,because they are pretty tricky and we haven't seen any use case from ourexperience. So we simply choose not to support them until they are provedusefully someday.

Lifecycle Management

A big difference between C++ and other more-modernlanguages is that C++ developers have to manage the lifecycle of objects ontheir own. The fact of an injected object being a real object or just a mockedone is totally transparent for CUTs (Class Under Test), so a mock object mightbe performed a deletion operation by the CUT in various design reasons.

Therefore, a useful mock framework should support that amock object is able to be deleted safely, and more: afterwards, the deletedmock object should still be able to verify whether the deletion actuallyhappens, since this operation is part of design, otherwise a memory leakagewill occur.

mockcpp supports this in a easy way:

  • If an injected mock object should be deleted by CUT, willBeDeleted() method of MockObject should be called before it's deleted by CUT;
  • If it should not be deleted by CUT, willKeepAlive() should be invoked; and
  • If the deletion is not a concern, you don't need to do anything.

willBeDeleted() and willKeepAlive() are used for thepurpose of verification. No matter they are called or not, the mock objectcould be deleted safely anyway.

Here is an example:

classTestGeneralTestFilter:public TESTCPP_NS::TestFixture
{
      MockObject<NameMatcher> testcaseMatcher;

public:

     
     void tearDown()
     {
           testcaseMatcher.reset();    
     }

     voidtest_should_delete_the_matcher_if_it_is_injected_as_composite_when_object_is_deleted()
     {
            GeneralTestFilter* filter=
                 newGeneralTestFilter(testcaseMatcher,true);

           testcaseMatcher.willBeDeleted();

            delete filter;

           testcaseMatcher.verify();
     }

     voidtest_should_not_delete_the_matcher_if_it_is_injected_as_non_composite_when_object_is_deleted()
     {
            GeneralTestFilter* filter=
                 newGeneralTestFilter(testcaseMatcher,false);

           testcaseMatcher.willKeepAlive();

            delete filter;

           testcaseMatcher.verify();
     }

     voidtest_should_be_able_to_tell_whether_a_test_case_matches_thepattern_hold_by_matcher()
     {
            GeneralTestFilter* filter=
                 newGeneralTestFilter(testcaseMatcher,false);

            MockObject<TestCase> testcase0;
            MockObject<TestCase> testcase1;

           MOCK_METHOD(testcaseMatcher, matches)
                  .stubs().with(eq((TestCaseInfoReader*)testcase0))
                  .will(returnValue(false));

           MOCK_METHOD(testcaseMatcher, matches)
                  .stubs().with(eq((TestCaseInfoReader*)testcase1))
                  .will(returnValue(true));

           TS_ASSERT_FALSE(filter->matches(testcase0));
           TS_ASSERT_TRUE(filter->matches(testcase1));

            delete filter;
     }

};

classGeneralTestFilter
{
public:
     
     GeneralTestFilter(NameMatcher* matcher,bool isComposite)
           : m_isComposite(isComposite), m_testcaseMatcher(matcher)
     {}
     
     ~GeneralTestFilter()
     {
           if(isComposite)
           {
                delete matcher;
           }
     }

     // other code

private:
     bool m_isComposite;
     NameMatcher* m_testcaseMatcher;
};

There are 3 test cases in this example, the first onetests that the injected matcher should be deleted with the destruction of theCUT object, because it was injected as composite; the second one tests theopposite; and the last one is used to test the other aspect of the CUT, itdoesn't care about the lifecycle of the injected matcher.

If an interface inherits from more than one otherinterfaces, and it's expected to be deleted by the injecting CUT, you shouldexplicitly points out that the injected mock object might be used as otherinterfaces. Because the CUT might delete it as a pointer of any interfaces itinherits, and it's better to keep the implementation of CUT black. mockcpp hasno way to deduce its inherited interface type unless you tell them to mockcpp,due to the lack of reflection of C++ language.

Once again, this feature should only be used inmulti-inheritance cases, although it does not harm at all in single-inheritancecases.

Here is an example:

structBase0
{
    virtualvoid base00()=0;
    virtual~Base0(){}
};

structBase1
{
    virtualvoid base01()=0;
    virtual~Base1(){}
};

structInterface:publicBase0,publicBase1
{
   virtualvoid foo()=0;
   virtual~Interface(){}
};

classTestBar:public TESTCPP_NS::TestFixture
{
public:

    void test_should_do_something()
    {
          MockObject<Interface> iface;

          Bar* bar=newBar(iface);

          iface.mightBeUsedAs<Base1>();

          iface.shouldBeDeleted();

          delete bar;

          iface.verify();
    }
};

Dynamic Cast

mockcpp supports dynamic_cast quite well. The behaviorof dynamic_cast-ing a mock object isexactly the same as dynamic cast-ing a normal C++object.

Take an example:

////////////////////////////////////////////////////////////////
structBillingObject
{
      enumType
      {
          UNKNOWN=0;
          CALL,
          SMS,
          INET
      };

      virtualType getType()const=0;

      virtual~BillingObject(){}
};

////////////////////////////////////////////////////////////////
structCall:publicBillingObject
{
      virtualconst std::string& getPhoneNumberOfOriginator()const=0;
      virtualconst std::string& getPhoneNumberOfTerminator()const=0;
      virtualunsignedint getDurationInSeconds()const=0;

      virtual~ShortMessage(){}
};

////////////////////////////////////////////////////////////////
structInternetAccess:publicBillingObject
{
      virtualconst std::string& getPhoneNumber()const=0;
      virtualunsignedint getDurationInSeconds()const=0;
      virtualunsignedint getTransferSize()const=0;

      virtual~InternetAccess(){}
};

////////////////////////////////////////////////////////////////
structBill
{
     unsignedchar type;
     std::string originator;
     unsignedint originatorFee;
     std::string terminator;
     unsignedint terminatorFee;
     unsignedint duration;
     unsignedint size;

     Bill()
           : type(BillingObject::UNKOWN)
           , originator("")
           , originatorFee(0)
           , teminator("")
           , terminatorFee(0)
           , duration(0)
           , size(0)
     {}
};

////////////////////////////////////////////////////////////////
classBillingManager
{
public:

      void billing(constBillingObject* billingObject,Bill& bill);

// Other methods and attributes
};

In this design, billing() always accepts anobject of type BillingObject. However, in itsimplementation, in order to generate bills correctly,billingObject will be downcast to aconcrete type according to the returned value of the method getType().

Here is the test fixture for testing BillManager:

classTestBillingManager:public TESTCPP_NS::TestFixture
{
public:
 
     voidtest_should_be_able_to_generate_bill_of_a_CALL_type_billing_object()
     {
           BillingManager manager;
           Bill bill;

           MockObject<Call> billingObject;

          MOCK_METHOD(billingObject, getType)
                    .stubs().will(returnValue(BillingObject::CALL));

          MOCK_METHOD(billingObject, getPhoneNumberOfOriginator)
                    .stubs().will(returnValue(std::string("123456789")));

          MOCK_METHOD(billingObject, getPhoneNumberOfTerminator)
                    .stubs().will(returnValue(std::string("987654321")));

          MOCK_METHOD(billingObject, getDurationInSeconds)
                    .stubs().will(returnValue((unsignedint)78));
           
          manager.billing(billingObject, bill);

          TS_ASSERT_EQUALS(BillingObject::CALL, bill.type);
           TS_ASSERT_EQUALS("123456789", bill.originator);      
          TS_ASSERT_EQUALS("987654321", bill.terminator);      
          TS_ASSERT_EQUALS(78, bill.duration);
          TS_ASSERT_EQUALS(0, bill.size);
          TS_ASSERT_EQUALS(80, bill.originatorFee);
          TS_ASSERT_EQUALS(20, bill.terminatorFee);
     }
};

The CUT code:

voidBillingManager::billing(constBillingObject* billingObject,Bill& bill)
{
      if(billingObject->getType()==BillingObject::CALL)
      {
            Call* call=dynamic_cast<Call*>(billingObject);
            // "call" should not be 0
            if(call==0)
            {
                 throwError("Invalid billing object");
            }

           bill.type= call->getType();
           bill.originator= call->getPhoneNumberOfOriginator();
           bill.terminator= call->getPhoneNumberOfTerminator();
           bill.duration= call->getDurationInSeconds();
           bill.size=0;
            unsignedint minutes=(bill.duration/60+(bill.duration%60>0?1:0));
           bill.originatorFee  = minutes*40;
           bill.terminatorFee  = minutes*10;
            return;
      }
      // Other code
};

Because the interface you mocked was Call, the parameter"pointer to billingObject" of billing() could be successfullydowncast to a pointer toCall. If you specify themocked interface as any other type, the downcast operation will return 0.

In previous example, according to the implementation ofthe method billing(), the following test casewill pass.

    void test_should_throw_an_Error_exception_if_the_object_type_mismatch()
     {
           BillingManager manager;
           Bill bill;

           MockObject<InternetAccess> billingObject;

          MOCK_METHOD(billingObject, getType)
                    .stubs().will(returnValue(BillingObject::CALL));
           
          TS_ASSERT_THROWS(manager.billing(billingObject, bill),Error);
     }

Please note that mock++ does not provide this capabitiesfor MSVC, due to the evil ABI definition of type info. ( it proves again that-- Micro$oft sucks, in terms of techniques. It should develop products only fornon-developers).

Object Assignment

The assignment operation of a mock object is not allowedin mockcpp (the method MockOject& operator=(const MockObject&) is private). It hasbasically no value to provide this operation, and it will bring tricky issuesand the complexity of implementation.

However, copy constructor is allowed, which means thisoperation is valid:

MockObject<Foo> foo1;
MockObject<Foo> foo2= foo1;

But this is not:

MockObject<Foo> foo1, foo2;
foo2 = foo1;

Before we explain the reason why one is allowed butanother is not, I'd like to take a few minutes to introduce the differencebetween the assignment operation and the copy constructor, in case you are notfamiliar with it.

For basic data types of C++, these two kinds ofoperations are basically identical, which means the effect of the following twoways are pretty much the same:

int a=10;

and

int a;
a =10;

However, for the abstract types of C++, the difference isbigger. In the example:

Foo foo1;
Foo foo2 = foo1;

The object foo1 is constructed with thedefault constructor Foo(). The object foo2 is constructed with thecopy constructor Foo(const Foo&).

However, in this example:

Foo foo1, foo2;
foo2 = foo1;

The object foo2 is firstly constructedwith the default constructor and then it's performed an assignment operation(the method Foo&operator=(const Foo&) is invoked) in thesecond statement.

The implications of these two ways are utterlydifference, the first way means object foo2 is initializedaccording to the value of the object foo1. The latter, however,means the object foo2 is changed to the valueof foo1.

Back to the issue of mockcpp, the disability of theassignment operation of a mock object implies a fact: You can construct it bothwith default constructor and copy constructor. However, once a mock object isconstructed, it's not allowed to be changed to another one any longer.

Besides, you must be aware of that, a mock objectconstructed with the default constructor is a freshly new object, but onesconstructed with the copy constructor are merely references to the originalmock object. For example:

MockObject<Foo> foo1;
MockObject<Foo> foo2= foo1;
MOCK_METHOD(foo2, foo).stubs().will(returnValue(10));

When you set a specification of the method foo() of object foo2, you are actuallysetting the specification to the method foo() of object foo1.

Note that, the usage of constructing a mock object withcopy constructor is not encouraged, mockcpp does not promise the safety ofcopy-constructed mock objects (no reference count is implemented), which means,once the referenced mock object is deleted, accessing the referencing mockobject might cause crash.

 


0 0
原创粉丝点击