Предположим, что у меня есть классы
class Inner {
public:
void doSomething();
};
class Outer {
public:
Outer(Inner *inner); // Dependency injection.
void callInner();
};
Надлежащее модульное тестирование говорит, что у меня должны быть тесты для Inner
. Затем у меня должны быть тесты для Outer
, который использует не реальный Inner
, а скорее MockInner
, чтобы я выполнял модульные тесты по функциональности, добавленной только Outer
вместо полного стека Outer
/Inner
.
Чтобы сделать это, Googletest, кажется, предлагает превратить Inner
в чистый абстрактный класс (интерфейс) следующим образом:
// Introduced merely for the sake of unit-testing.
struct InnerInterface {
void doSomething() = 0;
};
// Used in production.
class Inner : public InnerInterface {
public:
/* override */ void doSomething();
};
// Used in unit-tests.
class MockInner : public InnerInterface {
public:
/* override */ void doSomething();
};
class Outer {
public:
Outer(Inner *inner); // Dependency injection.
void callInner();
};
Итак, в производственном коде я использовал бы Outer(new Inner)
; в тесте Outer(new MockInner)
.
OK. Кажется красивым в теории, но когда я начал использовать эту идею во всем коде, я обнаружил, что создаю чистый абстрактный класс для каждого классного класса. Это очень много печатающих панелей, даже если вы можете игнорировать незначительную производительность во время выполнения, что может привести к ненужной виртуальной отправке.
Альтернативный подход заключается в использовании шаблонов, как в следующем:
class Inner {
public:
void doSomething();
};
class MockInner {
public:
void doSomething();
};
template<class I>
class Outer {
public:
Outer(I *inner);
void callInner();
};
// In production, use
Outer<Inner> obj;
// In test, use
Outer<MockInner> test_obj;
Это позволяет избежать котельного покрытия и ненужной виртуальной отправки; но теперь вся моя кодовая база находится в больших файлах заголовков, что делает невозможным скрытие исходных реализаций (не говоря уже о том, что приходится расстраивать ошибки компиляции шаблона и длительное время сборки).
Являются ли эти два метода, виртуальные и шаблоны единственными способами для правильного модульного тестирования? Существуют ли лучшие способы проведения надлежащего модульного тестирования?
При правильном модульном тестировании я имею в виду каждый unit тест проверяет только функциональные возможности, введенные этим устройством, но не зависимые от него также.