如何在运行时使用 GoogleMock 在 C++ 单元测试中模拟系统调用?

raj*_*123 3 c c++ unit-testing googletest googlemock

我有一个用 C 编写的模块。其中我的 API 定义如下。

int ReadData(void* data, int data_size, char* filename, int offset)
Run Code Online (Sandbox Code Playgroud)

在执行单元测试以覆盖完整覆盖范围时,我需要遇到 fseek 和 fread 系统调用的错误条件。有人可以解释一下我如何在特定测试用例期间重写 fseek/fread ,以便它不会为该测试用例调用模拟 fseek/fread ,而不是系统调用?

ReadAPI.c
Run Code Online (Sandbox Code Playgroud)
#include<stdio.h>
#include<stdlib.h>

int readdata(void* data,int size, int offset, char* filename)
{
    if(data == NULL || size == 0 || filename == NULL)
    {
        return -1;
    }

    FILE* fp = fopen(filename,"rb");
    if(fp == NULL)
    {
        return -1;
    }

    if(fseek(fp,offset,SEEK_SET) != 0)
    {
        fclose(fp);
        return -1;
    }

    fread(data,size,1,fp);

    if(ferror(fp))
    {
        fclose(fp);
        return -1;
    }   
    fclose(fp);
    return 1;
}


Run Code Online (Sandbox Code Playgroud)
readapi.h
Run Code Online (Sandbox Code Playgroud)
#include<stdio.h>
#include<stdlib.h>

int readdata(void* data,int size, int offset, char* filename)
Run Code Online (Sandbox Code Playgroud)
Test.cpp
Run Code Online (Sandbox Code Playgroud)
#include<gtest/gtest.h>
#include"readapi.h"


TEST(test, Test1fseek)
{
    // When fseek called in readdata API call mock fseek to hit fseek fail  
    // fseek(){return -1;}
    char data[10] = {0};
    int status = readdata(data,sizeof(data),0,"test.txt");
    EXPECT_EQ(status, -1);  
}

TEST(test, Test2fread)
{
    //When fread called in readdata API call mock fread to hit read fail  
    // fread(){return -1;}
    char data[10] = {0};
    int status = readdata(data,sizeof(data),0,"test.txt");
    EXPECT_EQ(status, -1);  
}

int main()
{
    return RUN_ALL_TEST();
}


Run Code Online (Sandbox Code Playgroud)

Ari*_*Ari 5

你可以参考Gmock的文档中的mocking-free-functions

不幸的是,使用推荐的方法意味着您将不得不更改代码,如果您的代码需要严格成为代码,则这可能不起作用C

但是,如果您愿意接受这一点,根据文档,您将必须围绕您正在使用的所有系统函数创建一个包装器,然后模拟该包装器。

另外,不要忘记,您必须为所有预期调用的函数添加EXPECT_CALLor并返回非默认(即 0)值。ON_CALL例如,在您的第一个测试中,您还应该提供EXPECT_CALLor ON_CALLfor fopen

这是一个示例实现:

// System wrapper interface class.
class MySystemWrapperInterface {
 public:
  virtual FILE* fopen(const char* filename, const char* mode) = 0;
  virtual int fseek(FILE* stream, long int offset, int whence) = 0;
  virtual size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) = 0;
  virtual int ferror(FILE* stream) = 0;
  virtual int fclose(FILE* stream) = 0;
};

// System wrapper actual class used in production code.
class MySystemWrapperClass : public MySystemWrapperInterface {
 public:
  FILE* fopen(const char* filename, const char* mode) {
    return ::fopen(filename, mode);
  }
  int fseek(FILE* stream, long int offset, int whence) {
    return ::fseek(stream, offset, whence);
  }
  size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) {
    return ::fread(ptr, size, nmemb, stream);
  }
  int ferror(FILE* stream) { return ::ferror(stream); }
  int fclose(FILE* stream) { return ::fclose(stream); }
};

// Mocked System wrapper used for testint.
class MySystemWrapperMockClass : public MySystemWrapperInterface {
 public:
  MOCK_METHOD(FILE*, fopen, (const char*, const char*), (override));
  MOCK_METHOD(int, fseek, (FILE*, long int, int), (override));
  MOCK_METHOD(size_t, fread, (void*, size_t, size_t, FILE*), (override));
  MOCK_METHOD(int, ferror, (FILE*), (override));
  MOCK_METHOD(int, fclose, (FILE*), (override));
};

// Wrapper class for your own readdata function.
class MyClass {
  // The system wrapper passed by dependency injection through constructor.
  MySystemWrapperInterface* system_wrapper_;

 public:
  // Initialize the system wrapper in constructor.
  MyClass(MySystemWrapperInterface* system_wrapper)
      : system_wrapper_(system_wrapper) {}

  int readdata(void* data, int size, int offset, char* filename) {
    if (data == NULL || size == 0 || filename == NULL) {
      return -1;
    }

    FILE* fp = system_wrapper_->fopen(filename, "rb");
    if (fp == NULL) {
      return -1;
    }

    if (system_wrapper_->fseek(fp, offset, SEEK_SET) != 0) {
      system_wrapper_->fclose(fp);
      return -1;
    }

    system_wrapper_->fread(data, size, 1, fp);

    if (system_wrapper_->ferror(fp)) {
      system_wrapper_->fclose(fp);
      return -1;
    }
    system_wrapper_->fclose(fp);
    return 1;
  }
};

TEST(test, Test1fseek) {
  // Create the mock object and inject it into your class.
  MySystemWrapperMockClass mock_system_wrapper;
  MyClass my_object(&mock_system_wrapper);

  // When fseek called in readdata API call mock fseek to hit fseek fail
  // fseek(){return -1;}

  // IMPORTANT: Don't forget to add EXPECT_CALL or ON_CALL for all functions
  // that are expected to be called.
  EXPECT_CALL(mock_system_wrapper, fopen)
      .Times(1)
      .WillOnce(Return(reinterpret_cast<FILE*>(0x1)));

  EXPECT_CALL(mock_system_wrapper, fseek).Times(1).WillOnce(Return(1));
  EXPECT_CALL(mock_system_wrapper, fclose).Times(1).WillOnce(Return(1));

  char data[10] = {0};
  int status = my_object.readdata(data, sizeof(data), 0, "test.txt");
  EXPECT_EQ(status, -1);
}

TEST(test, Test2fread) {
  // Create the mock object and inject it into your class.
  MySystemWrapperMockClass mock_system_wrapper;
  MyClass my_object(&mock_system_wrapper);

  // When fread called in readdata API call mock fread to hit read fail
  // fread(){return -1;}
  // IMPORTANT: Don't forget to add EXPECT_CALL or ON_CALL for all functions
  // that are expected to be called.
  EXPECT_CALL(mock_system_wrapper, fopen)
      .Times(1)
      .WillOnce(Return(reinterpret_cast<FILE*>(0x1)));
  EXPECT_CALL(mock_system_wrapper, fseek).Times(1).WillOnce(Return(0));
  EXPECT_CALL(mock_system_wrapper, fread).Times(1).WillOnce(Return(-1));
  EXPECT_CALL(mock_system_wrapper, ferror).Times(1).WillOnce(Return(-1));
  EXPECT_CALL(mock_system_wrapper, fclose).Times(1).WillOnce(Return(1));

  char data[10] = {0};
  int status = my_object.readdata(data, sizeof(data), 0, "test.txt");
  EXPECT_EQ(status, -1);
}
Run Code Online (Sandbox Code Playgroud)

实例: https: //godbolt.org/z/qxf74fWGh