Легко писать макеты, которые держат состояние
Я тестирую некоторый код, который интенсивно использует обратные вызовы, он выглядит примерно так:
class Client {
public:
Client(Socket socket) {
socket->onA([this] {
// call private Client methods....
});
socket->onB([this] {
// call private Client methods...
});
// repeated for onC, onD, and onE
}
void Start();
void Stop();
private:
// and almost all of the state is private
};
который почти полностью связывается с внешним миром посредством интерфейса, предоставляемого Socket
, который выглядит как:
class Socket {
public:
void onA(std::function<void()> callBack) = 0;
void onB(std::function<void()> callBack) = 0;
void onC(std::function<void()> callBack) = 0;
void onD(std::function<void()> callBack) = 0;
void onE(std::function<void()> callBack) = 0;
// various other public methods
};
Важно, чтобы я мог убедиться, что onX
функции были вызваны, и получить доступ к аргументам, которые были переданы им (потому что Client
изменения состояния в ответ на уведомления от Socket
Мне нужно смоделировать их в моих тестах).
Я могу написать макет, который выглядит так:
class MockSocket : public Socket {
public:
MOCK_METHOD1(onA, void(std::function<void()> callBack));
MOCK_METHOD1(onB, void(std::function<void()> callBack));
// etc
};
но я должен быть в состоянии написать ожидания в форме: "ожидать, что это MockSocket
объект имел свое onA
вызванный метод, теперь вызовите функцию, которая была передана в качестве аргумента onA
, Теперь проверьте, что write
метод Socket
был вызван с этой строкой:...."
В другом случае, когда у меня была только одна функция такого рода, я делал что-то вроде:
class MockSocket : public Socket {
public:
// as before
void setCallbackForA(std::function<void()> callBack) {
callbackForA = callBack;
}
void callCallbackForA() {
callbackForA();
}
// etc
private:
std::function<void()> callbackForA;
std::function<void()> callbackForB;
// etc
};
Но это будет смешное количество шаблонов, чтобы сделать это (5 геттеров и 5 сеттеров + все EXPECT_CALL
s, что очень немного от тестового к тестовому сценарию), и я подозреваю, что должен быть лучший способ сделать это (не прибегая к макросам в стиле C). Есть ли какая-то магия шаблонов, которая может помочь? Что-нибудь, чтобы сделать это немного легче иметь дело?
1 ответ
Я предполагаю, что вы тестируете Client
класс - так что вам действительно нужно хранить функции обратного вызова, которые Client
регистрируется в Socket
, Сделать это SaveArg<N>
действие. Смотрите код:
class ClientTest : public ::testing::Test
{
public:
std::function<void()> onACallback;
std::function<void()> onBCallback;
//...
MockSocket mockSocket;
std::unique_ptr<Client> objectUnderTest;
void SetUp() override
{
using ::testing::SaveArg;
EXPECT_CALL(mockSocket, onA(_)).WillOnce(SaveArg<0>(&onACallback));
EXPECT_CALL(mockSocket, onB(_)).WillOnce(SaveArg<0>(&onBCallback));
//...
objectUnderTest = std::make_unique<Client>(mockSocket);
}
};
С классом Test выше - вы можете собрать все обратные вызовы. И вы можете использовать эти отслеживаемые обратные вызовы для запуска действий в вашем классе Client, как в тестовом примере ниже:
TEST_F(ClientTest, shallDoThisAndThatOnA)
{
// here you put some expectations
//...
onACallback(); // here you trigger your Client object under test
// now you make some assertions (post actions)
//...
}