Как смоделировать системные вызовы 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 У этого подхода есть свои недостатки. В конце теста он скажет, что ваш код уходит), так что вам решать, как это исправить.