Kann gmock zum Stubben von C-Funktionen verwendet werden?

Lesezeit: 8 Minuten

Benutzer-Avatar
SRehman

Ich bin neu bei Gmock, daher möchte ich wissen, wie ich eine einfache C-Funktion, die in einer zu testenden Funktion für Unit-Tests aufgerufen wird, stubben kann.

Beispiel:

int func(int a)
{
  boolean find;
  // Some code
  find = func_1();
  return find;
}

Ich habe nach gmock gesucht und nach meinem Verständnis bietet gmock keine Funktionalität zum Stubben einfacher C-Funktionen, daher möchte ich fragen, ob gmock Funktionalität zum Spotten oder Stubben bietet func_1?

Wenn nicht, wie kann ich Stub func_1 manuell in meinen Testcode einfügen, ohne den Quellcode zu ändern? Ich verwende Google Test Framework für Unit-Tests.

Vielen Dank.

  • Ist func_1() auch eine ‘C’-Funktion?

    – Alter Fuchs

    13. August 2015 um 13:58 Uhr

  • @OldFox Ja, es ist eine C-Funktion.

    – SRehman

    13. August 2015 um 14:00 Uhr

  • Ist func_1() stellt ein komplexes Szenario bereit oder verwendet eine nicht testbare Abhängigkeit? (z. B. Hardware)

    – Alter Fuchs

    13. August 2015 um 14:07 Uhr


  • Nun, ich habe gerade ein Beispiel als ‘func_1()’ gegeben. Es kann sowohl einfache als auch komplexe Szenarien implementieren. In meinem Fall gibt es jedoch keine nicht überprüfbare Abhängigkeit.

    – SRehman

    13. August 2015 um 14:11 Uhr

Benutzer-Avatar
Georg P.

Ich befand mich in letzter Zeit in der gleichen Situation. Ich musste Komponententests für in C geschriebene Bibliotheken schreiben, die wiederum Abhängigkeiten zu anderen ebenfalls in C geschriebenen Bibliotheken hatten. Also wollte ich alle Funktionsaufrufe von Abhängigkeiten mit mocken gmock. Lassen Sie mich meine Vorgehensweise an einem Beispiel erläutern.

Angenommen, der zu testende Code (Bibliothek A) ruft eine Funktion aus einer anderen Bibliothek auf, lib_x_function():

lib_a_function()
{
   ...
   retval = lib_x_function();
   ...
}

Ich möchte also die Bibliothek X mocken. Dazu schreibe ich eine Interface-Klasse und eine Mock-Klasse in eine Datei lib_x_mock.h:

class LibXInterface {
public:
   virtual ~LibXInterface() {}
   virtual int lib_x_function() = 0;
}

class LibXMock : public LibXInterface {
public:
   virtual ~LibXMock() {}
   MOCK_METHOD0(lib_x_function, int());
}

Zusätzlich erstelle ich eine Quelldatei (sagen wir, lib_x_mock.cc), die einen Stub für die eigentliche C-Funktion definiert. Dies soll die Mock-Methode aufrufen. Beachten Sie das extern
Verweis auf das Mock-Objekt.

#include lib_x.h
#include lib_x_mock.h
extern LibXMock LibXMockObj;    /* This is just a declaration! The actual
                                   mock obj must be defined globally in your
                                   test file. */

int lib_x_function()
{
    return LibXMockObj.lib_x_function();
}

Jetzt muss ich in der Testdatei, die die Bibliothek A testet, das Mock-Objekt definieren
globalsodass es sowohl innerhalb Ihrer Tests als auch von erreichbar ist
lib_x_mock.cc. Dies ist lib_a_tests.cc:

#include lib_x_mock.h

LibXMock LibXMockObj;  /* This is now the actual definition of the mock obj */

...
TEST_F(foo, bar)
{
   EXPECT_CALL(LibXMockObj, lib_x_function());
   ...
}

Dieser Ansatz funktioniert perfekt für mich, und ich habe Dutzende von Tests und mehrere nachgeahmte Bibliotheken. Ich habe jedoch einige Zweifel, ob es in Ordnung ist, ein globales Scheinobjekt zu erstellen. Ich habe dies in einer separaten Frage gestellt und warte immer noch auf Antworten. Abgesehen davon bin ich mit der Lösung zufrieden.


AKTUALISIEREN: Das Problem mit dem globalen Objekt kann leicht behoben werden, indem das Objekt zB im Konstruktor der Testvorrichtung erstellt wird und einfach ein Zeiger auf dieses Objekt in einer globalen Variablen gespeichert wird.

Beachten Sie jedoch auch meine alternative Antwort auf diese Frage, die ich gerade gepostet habe.

  • Ich habe Ihre Vorgehensweise befolgt. Der einzige Unterschied besteht darin, dass Ihre angegebene Funktion keinen Parameter enthält. Im Gegensatz dazu zwei Parameter in meiner Funktion (einer ist struct und ein anderer ist int). In der Schnittstelle, virtual DltReturnValue dlt_client_connect(DltClient *client, int verbose) = 0; und in der Testdatei TEST_F(DltLogClient_test, init) { DltClient *client = new DltClient(); EXPECT_CALL(MockXIFObj, dlt_client_init(client, 0)); EXPECT_EQ(0, dltLogclient->init()); } Ich erhalte einen SEGFAULT. Wo habe ich mich geirrt?

    – Nibir

    6. März 2018 um 8:54 Uhr


  • Dies stürzt ab, weil das Scheinobjekt nach Abschluss der Tests zerstört wird, aber es sollte als Teil des Tests zerstört werden. Die Matthandi-Lösung ist also richtig und diese nicht.

    – Daid Braam

    18. März 2020 um 9:38 Uhr

  • Wenn dies zu einem Absturz führt, hat dies etwas mit Ihrem Code zu tun. Aber wie ich in der Bearbeitung ganz unten geschrieben habe, können Sie das Scheinobjekt einfach zusammen mit der Testvorrichtung konstruieren und zerstören lassen. Das sollte dein Problem lösen.

    – Georg P.

    18. März 2020 um 19:21 Uhr

Dies ist eine weitere Antwort von mir auf diese Frage. In den zwei Jahren, die seit der ersten Antwort vergangen sind, habe ich verstanden, dass GMock einfach das falsche Framework zum Mocken von C-Funktionen ist. In Situationen, in denen Sie viele Funktionen zum Spotten haben, ist meine zuvor gepostete Antwort einfach zu umständlich. Der Grund ist, dass GMock verwendet Objektnähte Produktionscode durch Scheincode ersetzen. Dies beruht auf polymorphen Klassen, die es in C nicht gibt.

Um C-Funktionen nachzuahmen, sollten Sie stattdessen verwenden Verbindungsnähte, die den Produktionscode zum Zeitpunkt der Verknüpfung durch den Scheincode ersetzen. Zu diesem Zweck gibt es mehrere Frameworks, aber mein Favorit ist das Gefälschtes Funktionsframework (F F F). Probieren Sie es aus, es ist viel einfacher als GMock. Es funktioniert auch perfekt in C++-Anwendungen.

Für Interessierte hier ein guter Artikel von Michael Feathers über die verschiedenen Nahtarten.

Ich habe schon lange nach einer Lösung gesucht, um alte C-Funktionen mit googleMock zu simulieren, ohne bestehenden Code zu ändern, und in den letzten Tagen bin ich auf den folgenden wirklich großartigen Artikel gestoßen: https://www.codeproject.com/articles/1040972/using-googletest-and-googlemock-frameworks-for-emb

Heute habe ich meinen ersten Unit-Test für C-Funktionen mit gmock geschrieben und als Beispiel zwei Funktionen aus der Bibliothek bcm2835.c genommen (http://www.airspayce.com/mikem/bcm2835/) für die Himbeer-Pi-Programmierung: Hier ist meine Lösung: Ich verwende den gcc 4.8.3. unter Eclipse und Windows. Achten Sie darauf, die Compiler-Option -std=gnu++11 zu setzen.

Hier sind meine zu testenden Funktionen

int inits(void);
void pinMode(uint8_t pin, uint8_t mode);

int inits(){
    return bcm2835_init();
}

void pinMode(uint8_t pin, uint8_t mode){
    bcm2835_gpio_fsel(pin, mode);
}

Enthält und definiert Einheitentests mit googleTest / googleMock

// MOCKING C-Functions with GMOCK :)
#include <memory>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
using namespace ::testing;
using ::testing::Return;

Mock BCM2835Lib-Funktionen

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

    // mock methods
    MOCK_METHOD0(bcm2835_init,int());
    MOCK_METHOD2(bcm2835_gpio_fsel,void(uint8_t,uint8_t));
};

Erstellen Sie eine TestFixture

class TestFixture: public ::testing::Test{
public:
    TestFixture(){
        _bcm2835libMock.reset(new ::testing::NiceMock<BCM2835Lib_MOCK>());
    }
    ~TestFixture(){
        _bcm2835libMock.reset();
    }
    virtual void SetUp(){}
    virtual void TearDown(){}

    // pointer for accessing mocked library
    static std::unique_ptr<BCM2835Lib_MOCK> _bcm2835libMock;
};

Instanziieren Sie mocked lib-Funktionen

// instantiate mocked lib
std::unique_ptr<BCM2835Lib_MOCK> TestFixture::_bcm2835libMock;

Gefälschte Lib-Funktionen, um Mocks mit den C-Funktionen zu verbinden

// fake lib functions
int  bcm2835_init(){return TestFixture::_bcm2835libMock->bcm2835_init();}
void bcm2835_gpio_fsel(uint8_t pin, uint8_t mode){TestFixture::_bcm2835libMock->bcm2835_gpio_fsel(pin,mode);}

Erstellen Sie eine Unit-Test-Klasse für BCM2835 aus TestFixture

// create unit testing class for BCM2835 from TestFixture
class BCM2835LibUnitTest : public TestFixture{
public:
    BCM2835LibUnitTest(){
        // here you can put some initializations
    }
};

Schreiben Sie die Tests mit googleTest und googleMock

TEST_F(BCM2835LibUnitTest,inits){
    EXPECT_CALL(*_bcm2835libMock,bcm2835_init()).Times(1).WillOnce(Return(1));

    EXPECT_EQ(1,inits()) << "init must return 1";
}

TEST_F(BCM2835LibUnitTest,pinModeTest){

    EXPECT_CALL(*_bcm2835libMock,bcm2835_gpio_fsel( (uint8_t)RPI_V2_GPIO_P1_18
                                                   ,(uint8_t)BCM2835_GPIO_FSEL_OUTP
                                                  )
               )
               .Times(1)
               ;

    pinMode((uint8_t)RPI_V2_GPIO_P1_18,(uint8_t)BCM2835_GPIO_FSEL_OUTP);
}

Ergebnisse 🙂

[----------] 2 tests from BCM2835LibUnitTest
[ RUN      ] BCM2835LibUnitTest.inits
[       OK ] BCM2835LibUnitTest.inits (0 ms)
[ RUN      ] BCM2835LibUnitTest.pinModeTest
[       OK ] BCM2835LibUnitTest.pinModeTest (0 ms)
[----------] 2 tests from BCM2835LibUnitTest (0 ms total)

Hoffe, es wird helfen 🙂 – für mich ist das eine wirklich funktionierende Lösung.

  • Wo lebt der Code, den Sie für “Instantiate Mocked Lib Functions” gezeigt haben? In welcher .cpp-Datei ist das?

    – Bjornruffianer

    15. Mai 2018 um 19:27 Uhr


Du kannst den … benutzen Süße Bibliothek zum Nachahmen der C-Funktion im GoogleMock-Stil.
Es gibt ein vollständiges Beispiel im Repo, aber nur einen Vorgeschmack:

INSTALL_MOCK(close);
CUTIE_EXPECT_CALL(fclose, _).WillOnce(Return(i));

Ich hatte einen ähnlichen Fall in einem Projekt, bei dem ich Unit-Tests durchführte. Meine Lösung bestand darin, zwei Make-Dateien zu erstellen, eine für die Produktion und eine zum Testen.

Wenn die Funktion func_1() im Header ah definiert und in a.cpp implementiert ist, dann können Sie zum Testen eine neue Quelldatei a_testing.cpp hinzufügen, die alle Funktionen in ah als Stub implementiert. Kompilieren und verknüpfen Sie für Komponententests einfach mit a_testing.cpp anstelle von a.cpp, und der getestete Code ruft Ihren Stub auf.

In a_testing.cpp können Sie den Aufruf dann an ein Gmock-Objekt weiterleiten, das Erwartungen und Aktionen wie gewohnt basierend auf dem Zustand und den Parametern setzt.

Ich weiß, es ist nicht perfekt, aber es funktioniert und löst das Problem, ohne den Produktionscode oder die Schnittstellen zu ändern.

In jedem UT versuchen wir, ein bestimmtes Verhalten zu verifizieren.

Du solltest etwas vortäuschen, wenn es sehr schwer/unmöglich ist (wir müssen isolieren unsere Einheit)/viel Zeit (Laufzeit..) aufwenden, um ein bestimmtes Verhalten zu simulieren.

Die explizite Verwendung einer ‘C’-Funktion bedeutet, dass die Funktion von Ihrer Einheit getrennt ist (daher sollten Sie sich nicht darüber lustig machen …). In dieser Antwort erkläre ich die Initiative, die Methode so zu testen, wie sie ist (in der Bearbeitung ..). Meiner Meinung nach solltest du anrufen func mit den Parametern, die verursachen func_1 um das Verhalten zu simulieren, das Sie überprüfen möchten.

GMock basiert auf Compilation Fake(Makros), daher kann man so etwas nicht machen. Um C-Methoden zu fälschen, müssen Sie andere Tools verwenden, z Typemock Isolator++.

Wenn Sie nicht verwenden möchten Isolator++, dann sollten Sie Ihre Methode umgestalten; Veränderung func zu func(int a, <your pointer the function>) und verwenden Sie dann den Zeiger anstelle von func_1.

Mein Diagramm in dieser Antwort kann Ihnen bei der Entscheidung helfen, wie Sie mit Ihrem Fall umgehen.

1311890cookie-checkKann gmock zum Stubben von C-Funktionen verwendet werden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy