meekrosoft / fff

A testing micro framework for creating function test doubles
Other
749 stars 163 forks source link

Use of fff to mock c++ class member functions #99

Closed cakira closed 1 year ago

cakira commented 3 years ago

Is there anyone using fff to mock class member functions?

I'm running unit tests in a code for the Arduino framework, which use C++ objects. In order to make to code compile, right now I declared some stubs, for instance:

File test_ble_manager.cpp

...
void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic){}; // Stub to compile the code
void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic){}; // Stub to compile the code
...

But I would like to do something like: File test_ble_manager.cpp (this is an untested code)

FAKE_VOID_FUNC(BLECharacCB_onRead, BLECharacteristic *);
FAKE_VOID_FUNC(BLECharacCB_onWrite, BLECharacteristic *);

void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic){ BLECharacCB_onRead(pCharacteristic); };
void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic){ BLECharacCB_onWrite(pCharacteristic); };

So I could use BLECharacCB_onWrite_fake.call_count and other features.

My doubts are:

fgr-17 commented 3 years ago

hey man. gMock has two major drawbacks:

The second can be workarounded somehow, but the first is a big issue.

cakira commented 3 years ago

Gracias, @fgr-17 . So gMock isn't a good option to my projects.

As a proof of concept about using fff with C++, I put the following macros in one of my C++ file:

#define FAKE_VOID_CLASS_FUNC0(classname, funcname)                             \
    FAKE_VOID_FUNC(classname##__##funcname);                                   \
    void classname::funcname() { return classname##__##funcname(); }

#define FAKE_VALUE_CLASS_FUNC0(rettype, classname, funcname)                   \
    FAKE_VALUE_FUNC(rettype, classname##__##funcname);                         \
    rettype classname::funcname() { return classname##__##funcname(); }

#define FAKE_VALUE_CLASS_FUNC1(rettype, classname, funcname, arg0type)         \
    FAKE_VALUE_FUNC(rettype, classname##__##funcname, arg0type);               \
    rettype classname::funcname(arg0type arg0) {                               \
        return classname##__##funcname(arg0);                                  \
    }

And they worked as intended in my code, because I could use:

FAKE_VOID_CLASS_FUNC0(BLEAdvertising, start);
FAKE_VALUE_CLASS_FUNC0(BLEServer *, BLEDevice, createServer);
FAKE_VALUE_CLASS_FUNC1(BLEService *, BLEServer, createService, const char *);

Instead of:

FAKE_VOID_FUNC(BLEAdvertising__start);
FAKE_VALUE_FUNC(BLEServer *, BLEDevice__createServer);
FAKE_VALUE_FUNC(BLEService *, BLEServer__createService, const char *);

void BLEAdvertising::start() { return BLEAdvertising__start(); };
BLEServer *BLEDevice::createServer() { return BLEDevice__createServer(); }
BLEService *BLEServer::createService(const char *uuid) {
    return BLEServer__createService(uuid);
}

Limitation: It didn't work to mock up void BLEDevice::init(std::string deviceName) because when I tried to call FAKE_VOID_FUNC(BLEDevice__init, std::string), the compiler generated the warning below

warning: ‘void* memset(void*, int, size_t)’ clearing an object of type ‘BLEDevice__init_Fake’ {aka ‘struct BLEDevice__init_Fake’} with no trivial copy-assignment; use assignment or value-initialization instead [-Wclass-memaccess]

Understandably, one cannot apply memset() in a std::string variable.

leonardopsantos commented 1 year ago

FFF is not the right tool for mocking C++ code because of name-mangling.

You should be using a C++ mocking framework:

it has no support for single functions (outside a class) it requires member class to be declared as virtuals.

That's because both cases are usually a sign that your architecture isn't quite right. You can't use run-time polymorphism unless your your methods are declared as virtual. Using stand-alone functions means that you're abandoning OOP. If you're following the general software engineering practices, you won't have stand-alone functions and your concrete classes will be implementations of an interface class (they'll use override). C++ doesn't have an official designation for interfaces like C# does.

I understand that the paragraph above isn't always true for deeply embedded projects. I'll way that C++ isn't a good fit to those because C++ binaries tend to be larger, especially if you use templates extensively. You're probably better off using C with a well-designed architecture (in that case FFF will fit like a glove).

If your CPU has a MMU and lots of memory, you should be adhering to SW engineering practices and GTest's limitations won't be an issue.

If you have to use C++ and you can not use virtual functions, you can always create your own stubs, like in @cakira examples. I'd never hack the FFF header, I'd just wrap them on C++ stubs.

cakira commented 1 year ago

Almost two years have passed since I opened this ticket, and now I tend to think that I was trying to do too much with fff.

The truth is, I attempted to learn CppUMock and Google Mock, but for some reason, I find them less intuitive compared to ThrowTheSwitch/Unity + meekrosoft/fff. As a result, I ended up trying "to use a nail as a screw".

I'll try harder to learn CppUMock or a similar tool and leave Unity + fff for pure C projects.

leonardopsantos commented 1 year ago

As a last tidbit, I use Google Test + FFF to test C code. I find that much, much better than ThrowTheSwitch/Unity. I get an excellent test harness framework while still being able to mock C code with FFF. Just note that the code under test is C, not C++. When I have to test C++ code, I use GTest + GMock.