Мы пишем модульные тесты для существующей кодовой базы. Мы используем Google Test / Google Mock для тестов, C++ 11 и Eclipse CDT с компилятором gcc.
Один из наших классов объединяет Boost Socket. Он использовал его как экземпляр, но, к счастью, мы можем изменить существующую базу кода, и я изменил его на указатель и внедрил сокет как зависимость. Итак, я стал издеваться над вызовами сокета, но возникла проблема: функции Boost не виртуальные.
Я нашел документацию, показывающую, как имитировать невиртуальные функции с помощью внедрение высокопроизводительных зависимостей. Однако попытка реализовать его, как показано, не увенчалась успехом. Например, в документе говорится, что нужно «шаблонизировать свой код». Итак, для нашего класса, который использует сокет форсирования, я последовал их примеру. Я вставил:
template <class boost::asio::ip::udp::socket>
Это должно позволить нам вставить наш фиктивный класс вместо конкретного класса Boost. Я пробовал это перед классом и отдельно перед конструктором, принимающим сокет, в заголовке и файле реализации. В каждом месте я получаю массу ошибок, большинство из которых связаны со строками «нет соответствующего вызова функции» при вызове конструктора.
Ясно, что я что-то не так делаю. Кто-нибудь знает где-нибудь полный пример?
Обновлять: По запросу, вот что у нас сейчас есть:
GoogleMock имитирует невиртуальные функции
class PIngester : public IPIngester{
public:
// this is the method that takes the socket. It is the constructor, and the socket
// is a default parameter so the existing code will still work. We added the socket
// as a parameter specifically for unit testing. If no socket is provided, the
// constructor creates one. It only will ever create a concrete Boost
// Socket, not a mock one.
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<boost::asio::ip::udp::socket> socket = std::unique_ptr<boost::asio::ip::udp::socket>(nullptr));
...
Обновление 2
Я определил общий тип класса для шаблона, но это нарушает существующий код. Вот моя текущая версия:
class PIngester : public IPIngester{
public:
template <class Socket>
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
Я думаю, что это может быть запрет на параметр по умолчанию, но я не могу быть уверен. Сообщения об ошибках не очень полезны:
error: no matching function for call to ‘foonamespace::PIngester::PIngester(boost::asio::io_service&, foonamespace::Band&)’
new PIngester(ioService, band));
Это сообщение об ошибке связано с существующим кодом; похоже, он не распознает параметр по умолчанию.
Обновление 3
Я отказался от этого подхода и решил вместо этого написать Boost Socket Wrapper. Оболочка будет содержать фактический экземпляр Socket, а его методы будут прямым переходом к фактическому Socket. Функции оболочки будут виртуальными, а мой фиктивный объект будет унаследован от оболочки. Тогда мой фиктивный объект будет имитировать виртуальные функции оболочки.
Хорошо, добавил. Если я правильно понимаю, мне нужно создать шаблон только в том случае, если мне нужен способ использовать конкретный класс в производственном коде и мой макет класса в тестах? Я не думаю, что это будет проблемой, поскольку я создаю конкретный класс в конструкторе только тогда, когда сокет не передается, что уже делает производственный код. Я всегда пройду в тестах макет.





Проблема, как вы заметили, в том, что компилятор не может вывести тот тип, который Socket здесь должен представлять:
class PIngester : public IPIngester{
public:
template <class Socket>
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
Если вы попытаетесь создать объект этого класса без указания третьего аргумента (как в new PIngester(ioService, band)), что именно будет Socket?
Теперь нет возможности явно указать какой-либо параметр шаблона при вызове конструктора, поэтому вы не можете сделать что-то вроде new PIngester<boost::asio::ip::udp::socket>(ioService, band), если конструктор шаблонный.
Вот несколько способов (из потенциально многих), чтобы обойти это:
Вы можете создать шаблон для самого класса PIngester:
template <class Socket>
class PIngester : public IPIngester{
public:
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
Тогда вызов new PIngester<boost::asio::ip::udp::socket>(ioService, band) (или ваш фиктивный класс вместо boost::asio::ip::udp::socket) будет работать.
Вы можете определить параметр шаблона по умолчанию:
class PIngester : public IPIngester{
public:
template <class Socket = boost::asio::ip::udp::socket>
PIngester(boost::asio::io_service& ioService, Band band,
std::unique_ptr<Socket> socket = std::unique_ptr<Socket>(nullptr));
...
Тогда PIngester(ioService, band) всегда будет использовать этот класс Boost по умолчанию, и вам нужно будет явно передать некоторый socket, который представляет собой уникальный указатель на ваш фиктивный класс, если вы хотите использовать его в тестах.
Это отличные предложения, спасибо! Я дам им шанс.
Пробовал, но как у гидры отрезаю одну голову, появляются еще две. Теперь gcc сообщает, что у меня не может быть типа шаблона в качестве данных члена. Все нормально; Я собираюсь написать оболочку Boost Socket, которая будет иметь виртуальные функции, которые являются прямым переходом к фактическому Boost Socket. Мой фиктивный объект будет наследовать от оболочки и имитировать ее виртуальные функции.
Неплохая идея; это хороший подход для обработки сторонних классов.
Не могли бы вы показать нам, как вы это настроили? Мне
template <class boost::asio::ip::udp::socket>кажется, что вы определяете параметр шаблона как часть определения функции (в этом случае вы должны определить имя общего класса вместоboost::asio::ip::udp::socket). Если вы пытаетесь передать параметр шаблона в соответствии с документацией Google Mock, я бы ожидал увидеть что-то вродеTestFunction<boost::asio::ip::udp::socket>().