Как смоделировать системные вызовы fork и execlp в модульном тесте C++ с использованием фреймворка gmock?

У меня есть существующий код C++, который создает дочерний процесс с помощью системного вызова fork(). И дочерний процесс выполняет команду linux с помощью системного вызова execlp(). Теперь я хочу протестировать этот код, используя gmock framework со 100% покрытием кода. Я много гуглил, но я не получил никакого полного решения. Кто-нибудь может мне помочь с этим?

Это мой СУТ:

int someclass :: os_fork()
{
  pid_t pid = fork();
  if (pid == -1) {
  cout<<"fork() failed with errno" <<errno <<endl;
  return false;
  }

  if (pid == 0 && (execlp("ls", "ls", nullptr) != 0))
  {
  cout<<"child process failed with errno"<<errno<<endl;
  return false;
  }
  int ppid = pid;
  return 0;
}

Я хочу издеваться над системными вызовами fork() и execlp(). Как я мог это сделать?

1 ответ

Нельзя имитировать глобальные функции такими, какие они есть. Вместо этого вы можете добавить слой абстракции и обернуть системные вызовы в интерфейс:

class OsInterface {
    virtual pid_t fork() = 0;
}

class OsInterfaceImpl : public OsInterface {
    virtual pid_t fork() override 
    {
        return ::fork();
    }
}

class OsInterfaceMock : public OsInterface {
    MOCK_METHOD0(fork, pid_t());
}

Это позволяет выбрать реальный системный вызов или макет с помощью Dependency Injection. Поскольку вы не предоставили никакого кода, я не могу вам в этом помочь.

Обычно вы передаете указатель или ссылку на внедренный класс в конструктор класса, который должен его использовать, но есть несколько других методов, которые могли бы лучше подойти вашему проекту.

Дополнительным преимуществом является то, что этот код более открытый-закрытый: вы можете легко (ну, относительно легко) добавить, например, реализацию Windows без изменения существующего кода - вы просто предоставите другой класс, наследующий от OsInterface, Вам не нужно изменять какие-либо вызовы интерфейса в вашем производственном коде, просто измените внедренный класс.

Обратите внимание, что это не будет запускать никаких дополнительных процессов в модульных тестах, но это является преимуществом. Вы должны проверить код, запущенный в процессе, отдельно.

Немного поздно, но вы можете просто переопределить его и "внедрить" MockFunction, чтобы оправдать ожидания. Наверное, какая-то зацепка.

#define CALL_ORIGINAL -2    
testing::MockFunction<int()> forkHelp;

pid_t fork(void)
{
    auto mockResult = forkHelp.Call();
    if (CALL_ORIGINAL != mockResult)
    {
        return mockResult;
    }
    return syscall(SYS_fork); /* Hack for calling real syscall */
}

PS У этого подхода есть свои недостатки. В конце теста он скажет, что ваш код уходит), так что вам решать, как это исправить.

Другие вопросы по тегам