Bill discusses the benefits of test first programming in an instrumented C++ telecommunications application.

Introduction

Before I ever knew anything about XP, I was experimenting with various means of testing complex telecom applications using what I refer to as an “inside-out” approach. Most legacy testing techniques in this domain utilize some form of black box testing where the focus is on observed behavior. Problems are debugged using “trace” statements (essentially printf ) embedded in the methods or IDE debuggers when available or applicable. These techniques fall short when critical timing issues are involved and/or multiple threads are in play. The alternative that I started to explore involved a mechanism whereby the testability of the software was built in. In this way, knowing what’s going on inside is not an second order issue (or add on in the case of printf) but first order. The testability of the software is not a black box outside looking in concern, but a built in capability.

When I started learning about XP, it occurred to me that the test first practice could work synergistically with the “inside-out” approach. I built a framework that supports an automated unit test suite inter working with instrumented application code in C++. The results were better than expected. I guess you can say that this is a form of extreme XP.

Show Me The Code

The particular application where I applied these ideas involved the maintenance of ATM and IP network components in a next generation switching system. The software involved is written in C++ and utilizes CORBA interfaces and various client applications (one of which is a GUI). The core of the testing framework consisted of four parts:

  1. Control Script
  2. Test Code
  3. Application Code
  4. Communication Mechanism

The communication mechanism utilizes UDP to facilitate sending messages between the application and test code.

The control script is currently implemented using a simple ksh script:

...
teststr="remove"; label
xpmain rmvucl m${params[$test]} >/dev/null 2>> $LOG
if [ "$?" != 0 ]; then errorstr="Failed rmvucl"; fail; fi
teststr="reset"; label
xpmain reset m${params[$test]} >/dev/null 2>> $LOG
if [ "$?" != 0 ]; then errorstr="Failed reset"; fail; fi
...

In this example, a particular component is being removed from service and then reset. As you’ll see in the test and application code that follows, removing a component translates to specific method invocations (i.e., essentially unit level testing). The control script provides a high level mechanism to specify a functional/unit test. I think that incorporating XUnit into this framework would be a good idea in the future. For now, I have taken a simple approach that works well in my environment.

The test code for the “remove” test is as follows (in part):

class RmvTest : public TestBase
{
public:
        void init(CO_Condition cond, const CIMaintComponent_var& mobj, 
        const MsmName& name, unsigned int timeout, unsigned int mytimeout )
        {
                m_MsgManagerHelper.MM_Register(tapiRmvRealRsp, this, rmvRealization, 
                	MAKE_CONDCAST_LVL1(TestApiMsg, TestApiRmvRealizationRspMsg,
                	! m_MsmName, MsmName, name));
                m_MsgManagerHelper.MM_Register(tapiRmvRsp, this, rmvRsp);
                mobj->RMV(mscbobj, cond, timeout, CO_NoBlock, CO_Manual, 23);
                secTimeout = mytimeout;
        }
        void rmvRealization( TestApiMsg* msg )
        {
                TestApiRmvRealizationRspMsg* pMsg = 
                	static_cast<TestApiRmvRealizationRspMsg*>(msg);
                cerr << "Recevied remove realization" << endl;
                if( pMsg->m_Success )
                {
                        cerr << "Successful remove realization" << endl;
                }
                else finished(1);
        }
        void rmvRsp( TestApiMsg* msg )
        {
                TestApiRmvResponseMsg* pRmvMsg = static_cast<TestApiRmvResponseMsg*>(msg);
                cerr << "Received remove response" << endl;
                if( pRmvMsg->result == CO_CompleteSuccess )
                {
                        cerr << "Successful remove" << endl;
                        finished(0);
                }
                else finished(1);
        }
};

The message manager code in the ‘init’ method is invoking the communication framework. The application code will send this test code the ‘tapiRmvRealRsp’ and ‘tapiRmvRsp’ messages if all goes well. The ‘mobj->RMV(…)’ line is invoking the application code via a CORBA interface (i.e., simulating the GUI interface at this point). As you can see, the test code is quite simple. This code is invoked by the control script described above.

The application code for the “remove” test is as follows (in part again):

void MtceComponent::activeRealizationRsp( MrRemoveRspMsg& rspMsg )
{
        MrReturn ret;

        if( rspMsg.mrMsgStruct.mrStatusCode != mrCompleteSuccess )
        {
                ret.ret = mrFailure;
                ret.err = rspMsg.mrMsgStruct.mrErrorCode;
        }
        else
        {
                ret.ret = mrSuccess;
        }

        TESTAPI_RMVREALRSP( getMsmName(), ret.ret == mrSuccess );

        MrHookReplyMsg hookMsg(
                rspMsg.cm_hdr.source_name,
                rspMsg.cm_hdr.dest_name,
                rspMsg.cm_hdr.corr_id,
                rspMsg.cm_hdr.msg_importance,
                ret,
                0);

        Mr::processMsg( &hookMsg );
}

The key is the line with the ‘TESTAPI_RMVREALRSP()’ macro. This is the point in the application code where a message is sent to the test code (in this case ‘tapiRmvRealRsp’). The removal of a component is an asynchronous operation (i.e., the ‘mobj->RMV()’ method return immediately to the caller) the actual result of which is a bit complicated. However, by instrumenting the application code, we can verify this method at each step with relatively simple test code.

Conclusions

You might question whether we are really testing at the unit level or somewhere higher with this approach. I guess I don’t know for certain. I can tell you that this approach was implemented in a real project and was tested down stream using more traditional integration/system test methods. Very few errors were found by the testers and those that were turned out to be my fault for not writing a test in the first place (part of my learning process). There are tests now in place for those errors.

I haven’t seen much application of XP practices in the C++ real time world. I think that XP and its practices are sound and can translate well in this domain. I am anxious to apply some of the other practices as well (especially pair programming).

I would be glad to receive feedback and/or answer any questions. Please contact me at pyritz@lucent.com.