Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 58
Текст из файла (страница 58)
It’s best to add it to the bottom of the class declarationbecause it uses the public access specifier. There are separate definitionsfor DLLs and EXEs because the function must be exported from a DLL soit may be called from a separate module. The second function definedby __DECLARE_TEST, __DbgTest(), is designed to allow test codeto access non-public members of the class, if such testing is required.Because any test code will implement this function itself, it is notexported.The __DECLARE_TEST macro is defined to be the same for bothrelease and debug builds, because otherwise the export list for debugbuilds would have an extra function (__DbgTestInvariant()) compared to the release version.
This could cause binary compatibilityproblems, by changing the ordinal numbers of other exported functionsin the module. (Chapter 18 discusses binary compatibility in more detail.)The __TEST_INVARIANT macro is defined below; as you can see,it doesn’t call __DbgTestInvariant() in release builds. However, ifyou don’t want the invariance test code to be built into release builds, youshould surround it with preprocessor directives as appropriate to buildthe invariance testing into debug builds only.#if defined(_DEBUG)#define __TEST_INVARIANT __DbgTestInvariant()#else#define __TEST_INVARIANT#endifOBJECT INVARIANCE MACROS271The invariance test function, __DbgTestInvariant(), typicallychecks that the object’s state is correct at the beginning and end ofa function.
If the state is invalid, a panic should be raised by callingUser::Invariant() (which panics with category USER, reason 0).You may recall, from Chapter 16, that this is the panic raised by theASSERT macro. Thus a simple way to implement the invariance testingin __DbgTestInvariant() is to perform the invariance checks insideASSERT statements.Here’s an example of a class that represents a living person and usesthe __DECLARE_TEST invariance test to verify that:• the person has a gender• if the name is set, it is a valid string of length no greater than250 characters• the given age in years matches the age calculated for the given dateof birth and is no greater than 140 years.class CLivingPerson: public CBase{public:enum TGender {EMale, EFemale};public:static CLivingPerson* NewL(const TDesC& aName,CLivingPerson::TGender aGender);∼CLivingPerson();public:...
// Other methods omitted for clarityvoid SetDOBAndAge(const TTime& aDOB, const TInt aAge);private:CLivingPerson(TGender aGender);void ConstructL(const TDesC& aName);private:HBufC* iName;TGender iGender;TInt iAgeInYears;TTime iDOB; // Date of Birth__DECLARE_TEST; // Object invariance testing};CLivingPerson::CLivingPerson(TGender aGender): iGender(aGender) {}CLivingPerson::∼CLivingPerson(){delete iName;}CLivingPerson* CLivingPerson::NewL(const TDesC& aName,CLivingPerson::TGender aGender){CLivingPerson* me = new (ELeave) CLivingPerson(aGender);CleanupStack::PushL(me);me->ConstructL(aName);CleanupStack::Pop(me);272DEBUG MACROS AND TEST CLASSESreturn (me);}void CLivingPerson::ConstructL(const TDesC& aName){__TEST_INVARIANT;iName = aName.AllocL();__TEST_INVARIANT;}void CLivingPerson::SetDOBAndAge(const TTime& aDOB, const TInt aAge){// Store the DOB and age and check object invariance__TEST_INVARIANT;iDOB=aDOB;iAgeInYears=aAge;__TEST_INVARIANT;}void CLivingPerson::__DbgTestInvariant() const{#ifdef _DEBUG // Built into debug code onlyif (iName){// Name should be more than 0 but not longer than 250 charsTInt len = iName->Length();ASSERT(len>0); // A zero length name is invalidASSERT(len<250); // The name should be less than 250 characters}// Person should male or femaleASSERT((EMale==iGender)||(EFemale==iGender));if (iDOB>(TTime)0){// DOB is set, check that age is correct and reasonableTTime now;now.HomeTime();TTimeIntervalYears years = now.YearsFrom(iDOB);TInt ageCalc = years.Int(); // Calculate age in years todayASSERT(iAgeInYears==ageCalc);// Compare with the stored ageASSERT(iAgeInYears>0); // Stored value shouldn’t be 0 or lessASSERT(iAgeInYears<140); // A living person is less than 140}#endif}void TestPersonL(){_LIT(KSherlockHolmes, "Sherlock Holmes");CLivingPerson* holmes = CLivingPerson::NewL(KSherlockHolmes,CLivingPerson::EMale);// Holmes was (apparently) born on 6th January 1854, so he would// have been 150 years old at the time of writing (2004)TDateTime oldTime(1854, EJanuary, 5, 0, 1, 0, 0);// This will panic.
Holmes is older than 140!holmes->SetDOBAndAge(TTime(oldTime), 150);delete holmes;}CONSOLE TESTS USING RTest273One last point: if the class derives from a class which also implementsthe invariance test function, __DbgTestInvariant() should be calledfor the base class first, then any checking done on the derived object.17.3 Console Tests Using RTestClass RTest is not documented, but is a useful test utility class, used bya number of Symbian OS test harnesses. You’ll find the class definition in\epoc32\include\e32test.h.
You should bear in mind that, becausethe RTest API is not published, there are no firm guarantees that it willnot change in future releases of Symbian OS.RTest is useful for test code that runs in a simple console. You canuse the class to display text to the screen, wait for input, and checka conditional expression, rather like an assertion, raising a panic if theresult is false.The main methods of RTest are Start(), End() and Next(),which number the tests, and operator(), which evaluates the conditional expression.
The first call to Start() begins numbering at 001 andthe numbering is incremented each time Next() is called. Nested levelsof numbering can be achieved by using additional calls to Start(), asshown in the example. Each call to RTest::Start() must be matchedby a call to RTest::End().The following source code can be used as the basis of a simple consolebased test project, using targettype exe, as described in Chapter 13.Remember, if you want to use the cleanup stack in console-based testcode, or link against a component which does so, you will need to createa cleanup stack at the beginning of the test, as discussed in Chapter 3.Likewise, if you write or link against any code which uses active objects,you will need to create an active scheduler, as I described in Chapters 8and 9.#include <e32test.h>GLDEF_D RTest test(_L("CONSTEST"));TInt Test1(){test.Next(_L("Test1"));return (KErrNone);}TBool Test2(){test.Next(_L("Test2"));return (ETrue);}void RunTests(){274DEBUG MACROS AND TEST CLASSEStest.Start(_L("RunTests()"));TInt r = Test1();test(KErrNone==r);test(Test2());test.End();}TInt E32Main(){__UHEAP_MARK; // Heap checking, as described abovetest.Title();test.Start(_L("Console Test"));TRAPD(r,RunTests());test.End();test.Close();__UHEAP_MARKEND;return (KErrNone);}You’ll notice that I’m using the _L form of a literal descriptor in thetest code, despite mentioning in Chapter 5 that it is deprecated because itconstructs a temporary stack-based TPtrC object at runtime.
However,it’s acceptable to use it for debug and test code which is never releasedand, in this test example, I think it’s clearer than predefining a set of _LITliteral descriptors.The code above gives the following output:RTEST TITLE: CONSTEST X.XX(XXX) // Build version and release numberEpoc/32 X.XX(XXX)// Build version and release numberRTEST: Level 001Next test - Console TestRTEST: Level 001.01Next test - RunTests()RTEST: Level 001.02Next test - Test1RTEST: Level 001.03Next test - Test2RTEST: SUCCESS : CONSTEST test completed O.K.RTest::operator() can be used to evaluate boolean expressions;if the expression is false, a USER 84 panic occurs.
For example, ifTest2() above is modified to return EFalse, running the test givesfollowing output:RTEST TITLE: CONSTEST X.XX(XXX) // Build version and release numberEpoc/32 X.XX(XXX)// Build version and release numberCONSOLE TESTS USING RTest275RTEST: Level 001Next test - Console TestRTEST: Level 001.01Next test - RunTests()RTEST: Level 001.02Next test - Test1RTEST: Level 001.03Next test - Test2RTEST: Level 001.03: FAIL : CONSTEST failed check 1 in Constest.cpp at line number 21RTEST: Checkpoint-failYou’ll notice this #define statement in e32test.h:#define test(x) __test(x,__LINE__,__FILE__)This is useful, because, if you use a variable name of test for theRTest object as in the example, even if you use the simplest overloadof operator() which just takes the conditional expression, the actualoverload invoked includes the source file and line number at which thetest occurs.