Я использую Google Mock и пытаюсь смоделировать системные вызовы C ++ (в частности, функции C ++ 11 chrono).
Я знаю, что должен создать интерфейс, создать класс для реализации интерфейса для моей реальной реализации, а затем смоделировать интерфейс в моих тестах. Я пытаюсь написать встроенное приложение, поэтому этот уровень косвенности звучит для меня слишком дорого.
Каков наиболее эффективный и эффективный способ включения системных вызовов в Google Mock?
Нет, вам не нужно прибегать к ложным статическим классам — это один из многих вариантов.
Если вы находитесь во встроенной среде, в которой виртуальная диспетчеризация требует слишком много времени, или если оптимизатор компилятора / компоновщика для этой архитектуры действительно плохо справляется, то вы можете попробовать следующие 3 способа смоделировать вызовы платформы.
Предположим для простоты, что вы хотите смоделировать функцию в std::this_thread
пространство имен, как sleep_for(std::milliseconds)
,
Пример 0 — непроверяемая базовая линия
Без насмешек, давайте предположим, что ваш код выглядит так:
class untestable_class
{
public:
void some_function()
{
if (must_sleep())
{
auto sleep_duration = std::chrono::milliseconds(1000);
std::this_thread::sleep_for(sleep_duration);
}
}
};
Вы бы использовали этот класс так:
void use_untestable_class()
{
untestable_class instance;
instance.some_function();
}
Из-за зависимости от стандартной библиотеки sleep_for
функция, у вас есть зависимость платформы, которая делает some_function
трудно провести модульное тестирование, не выполняя из него интеграционный тест.
Пример 1 — Тестирование с использованием статической политики
Говоря нашему классу использовать определенный политика потоков используя шаблон класса, мы можем абстрагировать зависимость платформы в модульных тестах. Политика может быть как статической, так и экземплярной — они обе устраняют необходимость виртуальной отправки во время выполнения, и компилятору / компоновщику их довольно легко оптимизировать.
в статический У нас есть одна «реальная» политика, которая зависит от платформы:
struct system_thread_policy1
{
static void sleep_milliseconds(long milliseconds)
{
auto sleep_duration = std::chrono::milliseconds(milliseconds);
std::this_thread::sleep_for(sleep_duration);
}
};
У нас также есть «фиктивная» политика, которую мы можем контролировать в модульных тестах:
struct mock_thread_policy1
{
// Mock attributes to verify interactions.
static size_t sleep_milliseconds_count;
static size_t sleep_milliseconds_arg1;
// Resets the mock attributes before usage.
static void sleep_milliseconds_reset()
{
sleep_milliseconds_count = 0;
sleep_milliseconds_arg1 = 0;
}
static void sleep_milliseconds(size_t milliseconds)
{
sleep_milliseconds_count++;
sleep_milliseconds_arg1 = milliseconds;
}
};
// This is needed with MS compilers to keep all mock code in a header file.
__declspec(selectany) size_t mock_thread_policy1::sleep_milliseconds_count;
__declspec(selectany) size_t mock_thread_policy1::sleep_milliseconds_arg1;
Производственный класс, использующий политику, принимает тип политики в качестве параметра шаблона и вызывает его sleep_milliseconds
статически:
template <typename thread_policy>
class testable_class1
{
public:
void some_function()
{
if (must_sleep())
{
thread_policy::sleep_milliseconds(sleep_duration_milliseconds);
}
}
private:
enum { sleep_duration_milliseconds = 1000 };
};
В производственном коде testable_class1
создается с использованием «реальной» политики:
void use_testable_class1()
{
testable_class1<system_thread_policy1> instance;
instance.some_function();
}
В модульном тесте testable_class1
создается с использованием политики «mock»:
void test_testable_class1()
{
mock_thread_policy1::sleep_milliseconds_reset();
testable_class1<mock_thread_policy1> instance;
instance.some_function();
assert(mock_thread_policy1::sleep_milliseconds_count == 1);
assert(mock_thread_policy1::sleep_milliseconds_arg1 == 1000);
//assert("some observable behavior on instance");
}
Недостатки этого метода:
sleep_for
,Недостатки этого метода:
Пример 2 — Тестируемый с использованием политики экземпляров
в пример У нас есть одна «реальная» политика, которая зависит от платформы:
struct system_thread_policy2
{
void sleep_milliseconds(size_t milliseconds) const
{
auto sleep_duration = std::chrono::milliseconds(milliseconds);
std::this_thread::sleep_for(sleep_duration);
}
};
У нас также есть «фиктивная» политика, которую мы можем контролировать в модульных тестах:
struct mock_thread_policy2
{
mutable size_t sleep_milliseconds_count;
mutable size_t sleep_milliseconds_arg1;
mock_thread_policy2()
: sleep_milliseconds_count(0)
, sleep_milliseconds_arg1(0)
{
}
void sleep_milliseconds(size_t milliseconds) const
{
sleep_milliseconds_count++;
sleep_milliseconds_arg1 = milliseconds;
}
};
Производственный класс, использующий политику, принимает тип политики в качестве параметра шаблона, получает экземпляр политики, введенный в конструктор, и вызывает его sleep_milliseconds
:
template <typename thread_policy>
class testable_class2
{
public:
testable_class2(const thread_policy& policy = thread_policy()) : m_thread_policy(policy) { }
void some_function() const
{
if (must_sleep())
{
m_thread_policy.sleep_milliseconds(sleep_duration_milliseconds);
}
}
private:
// Needed since the thread policy is taken as a reference.
testable_class2(const testable_class2&);
testable_class2& operator=(const testable_class2&);
enum { sleep_duration_milliseconds = 1000 };
const thread_policy& m_thread_policy;
};
В производственном коде testable_class2
создается с использованием «реальной» политики:
void use_testable_class2()
{
const testable_class2<system_thread_policy2> instance;
instance.some_function();
}
В модульном тесте testable_class2
создается с использованием политики «mock»:
void test_testable_class2()
{
mock_thread_policy2 thread_policy;
const testable_class2<mock_thread_policy2> instance(thread_policy);
instance.some_function();
assert(thread_policy.sleep_milliseconds_count == 1);
assert(thread_policy.sleep_milliseconds_arg1 == 1000);
//assert("some observable behavior on instance");
}
Недостатки этого метода:
sleep_for
,
Недостатки этого метода:
testable_class2
) — если взаимодействия не нуждаются в проверке, политику можно передать по значению в конструкторе, и большая часть класса goo исчезнет.Пример 3 — Тестирование с использованием виртуальной политики
Это отличается от первых двух примеров тем, что это зависит от виртуальной диспетчеризации, но оставляет вероятную возможность для компилятора / компоновщика оптимизировать виртуальную диспетчеризацию, если он может обнаружить, что эксплуатируемый экземпляр имеет базовый тип.
Во-первых, у нас есть производство база класс, который использует «реальную» политику в не чистой виртуальной функции:
class testable_class3
{
public:
void some_function()
{
if (must_sleep())
{
sleep_milliseconds(sleep_duration_milliseconds);
}
}
private:
virtual void sleep_milliseconds(size_t milliseconds)
{
auto sleep_duration = std::chrono::milliseconds(milliseconds);
std::this_thread::sleep_for(sleep_duration);
}
enum { sleep_duration_milliseconds = 1000 };
};
Во-вторых, у нас есть полученный класс, который реализует «виртуальную» политику в виртуальной функции (своего рода шаблон проектирования Template Template):
class mock_testable_class3 : public testable_class3
{
public:
size_t sleep_milliseconds_count;
size_t sleep_milliseconds_arg1;
mock_testable_class3()
: sleep_milliseconds_count(0)
, sleep_milliseconds_arg1(0)
{
}
private:
virtual void sleep_milliseconds(size_t milliseconds)
{
sleep_milliseconds_count++;
sleep_milliseconds_arg1 = milliseconds;
}
};
В производственном коде testable_class3
просто создан как сам по себе:
void use_testable_class3()
{
// Lots of opportunities to optimize away the virtual dispatch.
testable_class3 instance;
instance.some_function();
}
В модульном тесте testable_class3
создается с использованием производного класса «mock»:
void test_testable_class3()
{
mock_testable_class3 mock_instance;
auto test_function = [](testable_class3& instance) { instance.some_function(); };
test_function(mock_instance);
assert(mock_instance.sleep_milliseconds_count == 1);
assert(mock_instance.sleep_milliseconds_arg1 == 1000);
//assert("some observable behavior on mock_instance");
}
Недостатки этого метода:
sleep_for
,Недостатки этого метода:
final
(C ++ 11), поскольку он должен быть разрешен для наследования, и это может повлиять на остальную часть дизайна класса, если сложность выше, чем в простом примере выше.Тестовый забег
Все вышеперечисленное можно проверить с помощью этого:
int _tmain(int argc, _TCHAR* argv[])
{
test_testable_class1();
test_testable_class2();
test_testable_class3();
return 0;
}
и полный работоспособный пример в http://pastebin.com/0qJaQVcD
Прежде всего, виртуальная рассылка не так уж дорога, поэтому вы можете заниматься микрооптимизацией. Даже для встроенной платформы.
Если вы действительно хотите избежать виртуальной отправки, вы можете использовать статический полиморфизм с шаблонами и внедрять системные вызовы через параметр шаблона. Что-то вроде этого :
struct SysCallCaller
{
static void doCall()
{
// really execute system call
}
private:
SysCallCaller();
};
struct SysCallMock
{
static void doCall()
{
// mock system call
}
};
template < typename SysCallType >
struct MyClass
{
void Foo()
{
SysCallType::doCall();
}
};
int main()
{
#ifdef CALL_MOCK
MyClass< SysCallMock > obj;
#else
MyClass< SysCallCaller > obj;
#endif
obj.Foo();
}
Прежде всего, старайтесь избегать одиночек. Это просто скрытые глобальные переменные.