मैं लिनक्स मानक पुस्तकालय से एक साधारण कार्य का नकल करने की कोशिश कर रहा हूं। strerror() त्रुटि संदेश देता है। यह मेरी लाइब्रेरी है जिसमें फंक्शन मॉक करने के लिए है:

~$ cat mylib.c
#include <string.h>
#include <stdio.h>

int myStrerror()
{
    int error_number = 0;

    char* buffer = strerror(error_number);
    fprintf(stdout, "Returned string =  '%s'\n", buffer);
    return 0;
}

#if defined (EXECUTABLE)
int main(int argc, char **argv)
{
    return myStrerror();
}
#endif

~$ g++ -pedantic-errors -Wall -c mylib.c

यह मेरा गूगल परीक्षण है:

~$ cat test_mylib.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"

int myStrerror();

class strerrorMock {
public:
    MOCK_METHOD(char*, strerror, (int));
};

strerrorMock strerrorMockObj;

char *strerror(int error_number) {
    return strerrorMockObj.strerror(error_number);
}

TEST(MockTestSuite, strerror)
{
    using ::testing::Return;

    char response[] = "mocked strerror function";

    EXPECT_CALL(strerrorMockObj, strerror(0))
        .WillOnce(Return(response));
    EXPECT_EQ(myStrerror(), 0);
    ::testing::Mock::VerifyAndClearExpectations(&strerrorMockObj);
}


int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

~$ g++ -pedantic-errors -Wall \
        -o test_mylib.a \
        -I"$BUILD_DIR"/googletest-src/googletest/include \
        -I"$BUILD_DIR"/googletest-src/googlemock/include \
        test_mylib.cpp \
        "$BUILD_DIR"/lib/libgtestd.a \
        "$BUILD_DIR"/lib/libgmockd.a \
        ./mylib.o \
        -lpthread

यह वही है जो यह सामान्य रूप से लौटाता है:

~$ ./mylib.a
Returned string = 'Success'

और परीक्षण चलाना यह देता है:

~$ ./test_mylib.a
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from MockTestSuite
[ RUN      ] MockTestSuite.strerror
Returned string = 'mocked strerror function'
[       OK ] MockTestSuite.strerror (0 ms)
[----------] 1 test from MockTestSuite (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

test_mylib.cpp:32: त्रुटि: यह नकली वस्तु (परीक्षण MockTestSuite.strerror में प्रयुक्त) को हटा दिया जाना चाहिए लेकिन कभी नहीं। इसका पता @0x56114aa239e0 है।
त्रुटि: प्रोग्राम से बाहर निकलने पर 1 लीक नकली वस्तु मिली। जब वस्तु नष्ट हो जाती है तो नकली वस्तु पर अपेक्षाएं सत्यापित होती हैं। मॉक लीक करने का मतलब है कि इसकी उम्मीदों की पुष्टि नहीं हुई है, जो आमतौर पर एक टेस्ट बग होता है। यदि आप वास्तव में किसी मॉक को लीक करने का इरादा रखते हैं, तो आप टेस्टिंग::Mock::AllowLeak(mock_object) का उपयोग करके इस त्रुटि को दबा सकते हैं, या आप नकली के बजाय नकली या स्टब का उपयोग कर सकते हैं।

मेमोरी लीक से बचने के लिए मुझे क्या करना चाहिए?

1
Ingo 18 फरवरी 2021, 16:01

2 जवाब

सबसे बढ़िया उत्तर

कुछ महीनों के बाद कई प्लेटफार्मों पर अपने पहले उत्तर के समाधान का उपयोग करके मैंने पाया कि यह बहुत स्थिर नहीं है। विशेष रूप से एमएस विंडोज़ पर मुझे परेशानी थी कि GoogleMock हमेशा मॉकिंग फ़ंक्शन नहीं ढूंढता है। इसलिए मैंने उत्पादन कोड के न्यूनतम संशोधन को स्वीकार करने और एक रैपर वर्ग का उपयोग करने का निर्णय लिया googletest द्वारा सुझाए गए मुफ़्त सिस्टम फ़ंक्शंस के लिए।

निम्नलिखित के साथ मुझे केवल उत्पादन कोड में एक हेडर फ़ाइल जोड़नी होगी और उदाहरण के लिए इसके सिस्टम कॉल को बदलना होगा:

# from
fd = fopen("openclose.txt", "a");
# to
fd = stdioif->fopen("openclose.txt", "a");

Microsoft Windows पर मैंने googletest को github से क्लोन किया है, इसे सेटिंग के साथ पावरशेल का उपयोग करके बनाया है cmake -S . -B build फिर cmake --build build --config MinSizeRel और इस संरचना का उपयोग करके इसकी मूल निर्देशिका में बने रहें:

├── build
│   └── lib
│       └── MinSizeRel
│           ├── gmock.lib
│           ├── gmock_main.lib
│           ├── gtest.lib
│           └── gtest_main.lib
├── include
│   └── stdioif.h
├── src
│   ├── main.cpp
│   ├── openclose.cpp
│   └── test_openclose.cpp
├── main.exe
├── main.obj
├── openclose.txt
├── test_openclose.exe
└── test_openclose.obj

यहाँ हेडर फ़ाइल है:

#ifndef INCLUDE_STDIOIF_H
#define INCLUDE_STDIOIF_H

#include <stdio.h>

class Istdio {
// Interface to stdio system calls
  public:
    virtual ~Istdio() {}
    virtual FILE* fopen(const char* pathname, const char* mode) = 0;
    virtual int fprintf(FILE* stream, const char* format) = 0;
    virtual int fclose(FILE* stream) = 0;
};


// Global pointer to the  current object (real or mocked), will be set by the
// constructor of the respective object.
Istdio* stdioif;


class Cstdio : public Istdio {
// Real class to call the system functions.
  public:
    virtual ~Cstdio() {}

    // With the constructor initialize the pointer to the interface that may be
    // overwritten to point to a mock object instead.
    Cstdio() { stdioif = this; }

    FILE* fopen(const char* pathname, const char* mode) override {
        return ::fopen(pathname, mode);
    }

    int fprintf(FILE* stream, const char* format) override {
        return ::fprintf(stream, format);
    }

    int fclose(FILE* stream) override {
    }
};

// This is the instance to call the system functions. This object is called
// with its pointer stdioif (see above) that is initialzed with the
// constructor. That pointer can be overwritten to point to a mock object
// instead.
Cstdio stdioObj;

/*
 * In the production code you must call it with, e.g.:

    stdioif->fopen(...)

 * The following class should be coppied to the test source. It is not a good
 * idea to move it here to the header. It uses googletest macros and you always
 * hove to compile the code with googletest even for production and not used.

class Mock_stdio : public Istdio {
// Class to mock the free system functions.
  public:
    virtual ~Mock_stdio() {}
    Mock_stdio() { stdioif = this; }
    MOCK_METHOD(FILE*, fopen, (const char* pathname, const char* mode), (override));
    MOCK_METHOD(int, fprintf, (FILE* stream, const char* format), (override));
    MOCK_METHOD(int, fclose, (FILE* stream), (override));
};

 * In a gtest you will instantiate the Mock class, prefered as protected member
 * variable for the whole testsuite:

    Mock_stdio mocked_stdio;

 *  and call it with: mocked_stdio.fopen(...) (prefered)
 *  or                    stdioif->fopen(...)
*/

#endif // INCLUDE_STDIOIF_H

यह सरल उदाहरण कार्यक्रम है:

#include "stdioif.h"

#include <iostream>

int openclose() {
    FILE* fd = nullptr;
    int rc = 0;

    fd = stdioif->fopen("openclose.txt", "a");
    if(fd == NULL) {
        std::cerr << "Error opening file\n";
        return 1;
    }

    rc = stdioif->fprintf(fd, "hello world :-)\n");
    if(rc < 0) {
        std::cerr << "Error appending to file with return code: " << rc << "\n";
        stdioif->fclose(fd);
        return rc;
    }

    rc = stdioif->fclose(fd);
    if(rc) {
        std::cerr << "Error closing file with return code: " << rc << "\n";
        return rc;
    }

    std::cout << "done.\n";
    return 0;
}

मैं इसे इसके साथ निष्पादित करता हूं:

#include "src/openclose.cpp"

int main() {
    return openclose();

परीक्षण कार्यक्रम इस तरह दिखता है:

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "stdioif.h"

#include "src/openclose.cpp"

using ::testing::_;
using ::testing::Return;


class Mock_stdio : public Istdio {
// Class to mock the free system functions.
  public:
    virtual ~Mock_stdio() {}
    Mock_stdio() { stdioif = this; }
    MOCK_METHOD(FILE*, fopen, (const char* pathname, const char* mode), (override));
    MOCK_METHOD(int, fprintf, (FILE* stream, const char* format), (override));
    MOCK_METHOD(int, fclose, (FILE* stream), (override));
};


class OpenCloseTestSuite: public ::testing::Test {
  protected:
    // Member variables of the whole testsuite: instantiate the mock objects.
    Mock_stdio mocked_stdio;
};


TEST_F(OpenCloseTestSuite, open_close) {

    EXPECT_CALL(mocked_stdio, fopen(_, _))
        .WillOnce(Return((FILE*)0x123456abcdef));

    EXPECT_CALL(mocked_stdio, fprintf(_,_))
        .WillOnce(Return(-1));

    EXPECT_CALL(mocked_stdio, fclose(_)).Times(1);

    // process unit
    EXPECT_EQ(openclose(), 0);
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

इसे माइक्रोसॉफ्ट विंडोज़ पर संकलित करने के लिए मैं इसका उपयोग करता हूं:

cl -nologo  /EHsc -I. -I.\include -I.\googletest\include -I.\googlemock\include .\build\lib\MinSizeRel\gtest.lib .\build\lib\MinSizeRel\gmock.lib .\src\[main.cpp | test_openclose.cpp]
0
Ingo 7 अक्टूबर 2021, 17:05

समस्या यह है कि हम सिस्टम लाइब्रेरी से एक निःशुल्क वैश्विक फ़ंक्शन strerror() का उपयोग करते हैं। Googlemock के साथ हमेशा की तरह इंटरफ़ेस द्वारा इसका मज़ाक नहीं उड़ाया जाता है। इसलिए हमें इंटरफेस की जरूरत नहीं है। हमें मॉक किए गए फ़ंक्शन को एक मुफ्त फ़ंक्शन के साथ कवर करना होगा जो कि सिस्टम फ़ंक्शन की तुलना में समान दायरे में होने के लिए वैश्विक होना चाहिए क्योंकि यह इसे बदल देगा। इसके साथ हम यही करते हैं:

strerrorMock strerrorMockObj;

char* strerror(int error_number) {
    return strerrorMockObj.strerror(error_number);
}

यहां नकली का उदाहरण strerrorMockObj फ़ंक्शन के भीतर कॉल करने योग्य वैश्विक दायरे में भी है। लेकिन स्पष्ट रूप से Googletest वैश्विक नकली वस्तु को नहीं हटा सकता जैसा कि त्रुटि संदेश में बताया गया है। एक समाधान जो मैंने पाया है वह है सामान्य रूप से परीक्षण मैक्रो के भीतर नकली वस्तु को तुरंत चालू करना और इसमें एक वैश्विक सूचक संग्रहीत करना ताकि फ़ंक्शन इसे संबोधित कर सके:

strerrorMock* ptrStrerrorMockObj;
char* strerror(int error_number) {
    return ptrStrerrorMockObj->strerror(error_number);
}

TEST(MockTestSuite, strerror)
{
    strerrorMock strerrorMockObj;
    ptrStrerrorMockObj = &strerrorMockObj;
...
}

फिर स्मृति रिसाव की शिकायत किए बिना पूरा परीक्षण कार्यक्रम इस तरह दिखता है:

~$ cat test_strerror.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"

int myStrerror();

class strerrorMock {
public:
    MOCK_METHOD(char*, strerror, (int));
};

strerrorMock* ptrStrerrorMockObj;
char* strerror(int error_number) {
    return ptrStrerrorMockObj->strerror(error_number);
}

TEST(MockTestSuite, strerror)
{
    using ::testing::Return;

    strerrorMock strerrorMockObj;
    ptrStrerrorMockObj = &strerrorMockObj;

    char mockedstr[] = "mocked strerror function";
    EXPECT_CALL(strerrorMockObj, strerror(0))
        .WillOnce(Return(mockedstr));
    EXPECT_EQ(myStrerror(), 0);
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
1
Ingo 17 मार्च 2021, 00:50