Monday, June 20, 2005

Unit Testing for Network Programming

Network program is error-prone to write and hard to test, how can we make our life easier with the help of unit testing? Let's start with short examples:

Which function is easier to test?


int funcion1(const char* filename)
{
...open and process file...
}

int function2(std::istream& stm)
{
...process file...
}


The answer is function2: it avoids the dependency on external resource, so you can create a string and pass it in (see more on C++ coding standard item 63).

This is a basic technique that every unit testing book will teach. To apply it to network programming, we need to choose a stubable high level abstraction (instead of the low level socket as integer).

I use ACE as network library. While it provides very nice high level abstractions like ACE_SOCK_Stream, those classes are not designed to be base class (members are not virtual). This is understandable since lots of network programmers prefer to get the best performance they can.

So we have two choices here:

1. Use C++ template, and make the substitution happen at compile time.

Or

2. Create a wrapper to provide dynamic polymorphism (C++ coding style item 64).

I choose the later since it provides more flexibility.

Here is what the code looks like:


class ISockStream
{
public:
virtual ~ISockStream(){}

virtual ssize_t send (const void *buf,
size_t n,
int flags = 0,
const ACE_Time_Value *timeout = NULL) = 0;

virtual ssize_t recv (void *buf,
size_t n,
int flags = 0,
const ACE_Time_Value *timeout = NULL) = 0;
};

template <class _ACE_PEER_STREAM>
class StreamAdapter : public ISockStream
{
protected:
_ACE_PEER_STREAM stream_;
public:
ssize_t send (const void *buf,
size_t n,
int flags = 0,
const ACE_Time_Value *timeout = NULL)
{
return stream_.send(buf, n, flags, timeout);
}

ssize_t recv (void *buf,
size_t n,
int flags = 0,
const ACE_Time_Value *timeout = NULL)
{
return stream_.recv(buf, n, flags, timeout);
}
};

class MockStream : public ISockStream
{
ssize_t send (const void *buf,
size_t len,
int flags = 0,
const ACE_Time_Value *timeout = NULL)
{
//save the data somewhere (std::string for example)
//so that we can test the result later.
}

ssize_t recv (void *buf,
size_t len,
int flags = 0,
const ACE_Time_Value *timeout = NULL)
{
//generate fake data,
//we can even trigger failure (return -1).
}

};


In real product, we are going to use things like StreamAdapter<ACE_SOCK_Stream>. While in unit testing, MockStream will be sufficient.

0 Comments:

Post a Comment

<< Home