Вопрос

I'm starting using CppUTest for some C/C++ projects. Especially the mocking extension sound good, but I'm currently struggling how to set up mocks in the right way. Assume a low level class for abstracting network socket communication.

My first method:

size_t CMockSocket::recv(void* buf, size_t len)
{
  return (size_t) mock().actualCall("recv")
      .withParameter("len", (int) len)
      .returnValue().getIntValue();
}

Setting up the expectation:

mock().expectOneCall("recv")
    .withParameter("len", 20)
    .andReturnValue(15);

So far so good. But what I would need is to give more data - in this case the 15 bytes I want to give back via buf pointer. I tried to use the .setData() and setDataObject() methods. But it looks like that functions stores the data in named variables, not with the expected function call. Therefore subsequent calls will override the preceding data, right?

My current problem is how can I pass additional returning data to the mocking method? My first aproach for this was to create a resulting class which contains both the return value (size_t) and the data buffer. Like this:

class CRecvResults
{
public:
  size_t m_returnValue;
  void* m_buffer;
  CRecvResults(size_t returnValue, void* buffer) :
    m_returnValue(returnValue), m_buffer(buffer) {}
  ~CRecvResults() {}
};

size_t CMockSocket::recv(void* buf, size_t len)
{
  CRecvResults* pResults =
      (CRecvResults*) mock().actualCall("recv")
      .withParameter("len", (int) len)
      .returnValue().getPointerValue());

  assert(pResults != NULL);
  assert(buf != NULL);
  assert(len >= pResults->m_buffer.length());
  memcpy(buf, pResults->m_buffer.data(), pResults->m_buffer.length());
  return pResults->m_returnValue;
}

And the expectation:

char* buffer = "some returning buffer content at least 15 chars long";
CRecvResults results(15, buffer);
mock().expectOneCall("recv")
      .withParameter("len", 20)
      .andReturnValue(&results);

Question: is this the right way or do I miss something? I need the buffer content because i need to test the result interpreter.

My second method:

bool CMockSocket::send(const void* buf, size_t len)
{
  return mock().actualCall("send")
      .withParameter("len", (int) len)
      .returnValue().getIntValue();
}

Setting up the expectation:

mock().expectOneCall("send")
    .withParameter("len", 20)
    .andReturnValue(true);

In this case I want to verify the outgoing data in buf, generated by the code under test. Therefore I need to return that buffer content (and length) to the test function. Like this:

class CSendResults
{
public:
  bool m_returnValue;
  char* m_buffer;
  size_t m_length;
  CSendResults(bool returnValue, char* buffer, size_t length) :
    m_returnValue(returnValue), m_buffer(buffer), m_length(length) {}
  ~CSendResults() {}
};

bool CMockSocket::send(const void* buf, size_t len)
{
  CSendResults* pResults =
      (CSendResults*) mock().actualCall("send")
      .returnValue().getPointerValue();

  assert(pResults != NULL);
  assert(buf != NULL);
  assert(pResults->m_length >= len);
  memcpy(pResults->m_buffer, buf, len);
  pResults->m_length = len;
  return pResults->m_returnValue;
}

And the expectation:

char buffer[1024];
CSendResults results(true, buffer, sizeof(buffer);
mock().expectOneCall("send")
      .withParameter("len", 20)
      .andReturnValue(&results);

// further checks for results content...

This looks very ugly and with a lot of overhead to me. Again my question: is this the right way or do I miss something?

And my final question: can it be that I'm testing the completely wrong way? Please let me know about your experiences and practices!

Это было полезно?

Решение

A new feature has been added to CppUTest, which lets you specify an output parameter for mocks. This is what it might look like:

{
    return (size_t) mock().actualCall("recv")
        .withOutputParameter("buf", buf)
        .withParameter("len", (int) len)
        .returnUnsignedLongIntValueOrDefault(0);
}

And the expectation might be specified like this:

char buffer[] = "blabla";

mock().expectOneCall("recv")
    .withOutputParameterReturning("buf", buffer, sizeof(buffer))
    .withParameter("len", sizeof(buffer))
    .andReturnValue(sizeof(buffer));

The contents of buffer will be copied to the caller of the mock via the output parameter. So the ugly workaround is no longer needed.

Другие советы

I am using the following ugly workaround. I define a structure type for the return value plus the additional data I need in my fake function. I allocate and fill one instance where I call expectOneCall() and pass it using ".andReturnValue()". In the fake function I get my data with "...returnValue().getPointerValue()", then I set the return value to struct->rc, and process my additional data (copy/compare it to buffer). Before exiting the fake function the structure is freed.

This way the additional data needed is bound to the parameter list of the function call.

Foltos

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top