2015-08-13 13 views
6

Sono nuovo di gmock, quindi voglio sapere come posso interrompere una semplice funzione C chiamata in una funzione sottoposta a test per Unit Testing.È possibile utilizzare gmock per eseguire lo stub delle funzioni C?

Esempio:

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

Ho cercato su gmock e nella mia comprensione gmock non fornisce funzionalità per stub semplici funzioni C, quindi voglio chiedere non gmock fornisce funzionalità di deridere o stub func_1?

In caso contrario, come posso eseguire lo stub func_1 manualmente nel mio codice di prova senza modificare il codice sorgente? Sto usando il framework di test di google per i test unitari.

Grazie.

+0

È 'func_1()' anche una funzione 'C'? –

+0

@OldFox Sì, è una funzione C. – user3159610

+0

'func_1()' fornisce uno scenario complesso o utilizza una dipendenza non verificabile? (Ad esempio l'hardware) –

risposta

0

In ogni UT stiamo provando a verificare un comportamento specifico.

Si dovrebbe fingere qualcosa quando è molto difficile/impossibile (abbiamo bisogno di isolare nostra unità)/spendere un sacco di tempo (tempo di esecuzione ..) per simulare un comportamento specifico.

L'utilizzo di una funzione "C" in modo esplicito indica che la funzione si trova a parte dell'unità (pertanto non è necessario prenderla in giro ..). Nella risposta this spiego l'iniziativa per testare il metodo così com'è (nella modifica ..). A mio parere, è necessario chiamare func con i parametri che causano func_1 per simulare il comportamento che si desidera verificare.

GMock si basa su compilation fake (macro), quindi non è possibile fare una cosa del genere. Per simulare metodi "C" devi usare strumenti diversi come Typemock Isolator++.

Se non si desidera utilizzare Isolator++, è necessario eseguire il refactoring del metodo; Modificare func a func(int a, <your pointer the function>) e quindi utilizzare il puntatore anziché func_1.

La mia tabella in this risposta potrebbe aiutare a decidere il modo di gestire il caso.

+0

Il grafico fornisce un buon punto di vista per quanto riguarda lo sforzo necessario per eseguire il test unitario. Ma ho alcune domande in mente. Cosa succede se 'func_1' punta a molte dipendenze (funzioni) e quindi verifica durante l'implementazione di' func_1', vale a dire che il test di integrazione potrebbe richiedere molto tempo. E se il codice sorgente 'func' non può essere cambiato. C'è un modo per dire al compilatore di puntare a una funzione di stub invece di implementare real 'func_1' senza modificare il codice sorgente? – user3159610

+0

@ user3159610 non c'è modo di dire al compilatore di stubare il metodo. Dal tuo ultimo commento sembra che il tuo metodo sia un codice legacy. Il motivo principale per utilizzare strumenti di tessitura del codice come "isolator ++" è il codice legacy. Se si tratta di un codice legacy, ti suggerirei di saltare i test o creare test di integrazione solo per i flussi principali. Sono d'accordo con te sui test di integrazione (che richiedono molto tempo), tuttavia se il codice degno di UT dovresti ottenere un compito di refactoring nello sprint o il denaro per gli strumenti .... –

0

Ho avuto un caso simile in un progetto che stavo testando unitamente. La mia soluzione era creare due file make, uno per la produzione e uno per i test.

Se la funzione func_1() è defindata nell'intestazione a.h e implementata in a.cpp, per il test è possibile aggiungere un nuovo file di origine a_testing.cpp, che implementerà tutte le funzioni in a.h come stub. Per l'unittest, basta compilare e collegare con a_testing.cpp anziché a.cpp e il codice testato chiamerà lo stub.

In a_testing.cpp è quindi possibile inoltrare la chiamata a un oggetto gmock che imposterà le aspettative e le azioni come al solito in base allo stato e ai parametri.

So che non è perfetto, ma funziona e risolve il problema senza cambiare codice di produzione o interfacce.

7

Mi sono ritrovato nella stessa situazione ultimamente. Ho dovuto scrivere dei test unitari per le librerie scritte in C, che a loro volta avevano dipendenze anche da altre librerie scritte in C. Quindi volevo prendere in giro tutte le chiamate di funzioni delle dipendenze usando gmock. Lascia che ti spieghi il mio approccio con un esempio.

Si supponga il codice da testare (biblioteca A) chiama una funzione da un'altra libreria, lib_x_function():

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

Quindi, voglio prendere in giro la X. libreria Perciò scrivo una classe di interfaccia e di un mock classe in un file 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()); 
} 

Inoltre ho creare un file sorgente (ad esempio, lib_x_mock.cc), che definisce uno stub per la funzione di vero e proprio C. Questo chiamerà il metodo simulato. Notare il riferimento extern all'oggetto fittizio.

#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(); 
} 

Ora, nel file di test, che verifica la biblioteca A, devo definire l'oggetto fittizio globalmente, in modo che sia raggiungibile nei test e da lib_x_mock.cc. Questo è 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()); 
    ... 
} 

questo approccio funziona perfettamente per me, e ho decine di test e diversi librerie deriso. Tuttavia, ho qualche dubbio se è giusto creare un oggetto fittizio globale - ho chiesto questo in un separate question e ancora attendere le risposte. Oltre a questo sono contento della soluzione.

+0

Ho seguito la tua procedura. L'unica differenza è che non ci sono parametri nella funzione data. Al contrario, due parametri nella mia funzione (uno è struct e un altro è int).Nell'interfaccia 'virtuale DltReturnValue dlt_client_connect (client DltClient *, int verbose) = 0;' e nel file di prova 'TEST_F (DltLogClient_test, init) { \t DltClient * client = new DltClient(); \t EXPECT_CALL (MockXIFObj, dlt_client_init (client, 0)); \t EXPECT_EQ (0, dltLogclient-> init()); } ' Ricevo un SEGFAULT. Dove ho sbagliato? – Nibir

2

che cercavo già da tempo per una soluzione per deridere legacy c-funzioni con googleMock senza modificare il codice esistente e l'ultimo giorno ho trovato il seguente davvero grande articolo: https://www.codeproject.com/articles/1040972/using-googletest-and-googlemock-frameworks-for-emb

Oggi ho scritto il mio primo test unità per c-functions che utilizza gmock e ha preso come esempio due funzioni dalla libreria bcm2835.c (http://www.airspayce.com/mikem/bcm2835/) per la programmazione di raspberry Pi: Ecco la mia soluzione: sto utilizzando gcc 4.8.3. sotto Eclipse e Windows. Essere consapevoli di impostare l'opzione del compilatore -std = gnu ++ 11.

Qui ci sono le mie funzioni da testare

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); 
} 

Include e definisce per unità di test con funzioni 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

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

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

Creare un 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; 
}; 

istanziare deriso funzioni lib

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

falsi funzioni lib collegare Mocks con le funzioni C

// 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);} 

Creare unità di classe di prova per BCM2835 da TestFixture

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

Scrivi i test utilizzando googleTest e 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); 
} 

Risultati :)

[----------] 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) 

auguro che possa aiutare:) - per me questa è una soluzione davvero funzionante.

Problemi correlati