Модульное тестирование: кодирование интерфейсов? - PullRequest
13 голосов
/ 08 июля 2011

В настоящее время мой проект состоит из различных конкретных классов.Теперь, когда я вхожу в модульное тестирование, похоже, что я должен создать интерфейс для каждого класса (фактически удваивая количество классов в моем проекте)?Я использую Google Mock в качестве основы для насмешек.См. Google Mock CookBook по интерфейсам .Если раньше у меня могли быть только классы Car и Engine, то теперь у меня были бы абстрактные классы (или интерфейсы C ++) Car и Engine, а затем классы реализации CarImplementation и EngineImpl или что-то еще.Это позволило бы мне заглушить зависимость Car от Engine.

При изучении этого вопроса я столкнулся с двумя направлениями:

  1. Используйте интерфейсы только в том случае, если вам может понадобиться более одной реализации данной абстракции и / или для использования в открытых API, поэтому в противном случае не создавайте интерфейсы без необходимости.

  2. Юнит-тестызаглушки / насмешки часто являются «другой реализацией», и поэтому, да, вы должны создавать интерфейсы.

При модульном тестировании я должен создавать интерфейс для каждогокласс в моем проекте?(Я склоняюсь к созданию интерфейсов для удобства тестирования)

Ответы [ 3 ]

5 голосов
/ 27 июля 2011

Думаю, у вас есть несколько вариантов. Как вы говорите, одним из вариантов является создание интерфейсов. Скажем, у вас есть классы

class Engine:
{
public:
    void start(){ };
};

class Car
{
public: 
    void start()
    {
        // do car specific stuff
        e_.start();

private:
    Engine e;
};

Чтобы ввести интерфейсы - вам нужно сменить автомобиль, чтобы взять двигатель

class Car
{
public: 
    Car(Engine* engine) :
    e_(engine)
    {}

    void start()
    {
        // do car specific stuff
        e_->start();

private:
    Engine *e_;
};

Если у вас есть только один тип двигателя - вы внезапно усложнили использование объектов Car (кто создает двигатели, кто владеет двигателями). В автомобилях много деталей, поэтому эта проблема будет продолжать расти.

Если вам нужны отдельные реализации, другой способ будет с шаблонами. Это устраняет необходимость в интерфейсах.

class Car<type EngineType = Engine>
{
public: 
    void start()
    {
        // do car specific stuff
        e_.start();

private:
    EngineType e;
};

В своих издевательствах вы могли бы создавать Автомобили со специализированными двигателями:

Car<MockEngine> testEngine;

Другой, другой подход, заключается в добавлении методов в Engine для проверки его, что-то вроде:

class Engine:
{
public:
    void start();
    bool hasStarted() const;
};

Затем вы можете добавить метод проверки в Car или наследовать от Car для проверки.

class TestCar : public Car
{
public:
    bool hasEngineStarted() { return e_.hasStarted(); }
};

Для этого потребуется изменить Двигатель с частного на защищенный в классе Car.

В зависимости от ситуации в реальном мире, будет зависеть, какое решение лучше. Кроме того, у каждого разработчика будет своя святая Грааль того, как, по их мнению, код должен тестироваться модульно. Мои личные взгляды - помнить клиента / клиента. Предположим, что ваши клиенты (возможно, другие разработчики в вашей команде) будут создавать автомобили и не заботятся о двигателях. Поэтому я не хотел бы раскрывать понятия Engines (внутренний класс для моей библиотеки) просто для того, чтобы можно было выполнить модульное тестирование. Я бы предпочел не создавать интерфейсы и тестировать два класса вместе (третий вариант, который я дал).

1 голос
/ 13 июля 2011

Есть две категории тестирования, касающиеся видимости реализации: тестирование черного ящика и тестирование белого ящика

  • тестирование черного ящика фокусируется на тестировании реализации через их интерфейсы и проверке настроек.к их спецификации.

  • тестирование белого ящика тестирует детальные детали реализации, которые НЕ ДОЛЖНЫ в целом быть доступными извне.Этот вид тестирования подтвердит, что компоненты реализации работают так, как задумано.Таким образом, их результаты в основном представляют интерес для разработчиков, пытающихся выяснить, что сломано, или нуждается в поддержкеВ проекте должны быть полностью модульные сами по себе.Прекрасно провести какую-то линию, когда группа классов узнает друг о друге.Они как группа могут представлять другим модулям с точки зрения некоторого класса интерфейса фасада.Тем не менее, вы все еще хотите иметь тестовые драйверы белого ящика внутри этого модуля со знаниями о деталях реализации.Следовательно, такого рода тестирование не подходит для макетов .

    Из этого тривиально следует, что вам не нужно иметь макеты или интерфейсы для всего.Просто возьмите высокоуровневые компоненты дизайна, которые реализуют фасадные интерфейсы и создайте для них макеты.Это даст вам приятное место, где фиктивное тестирование окупаетсяне быть полезным в долгосрочной перспективе

0 голосов
/ 08 июля 2011

Создание интерфейсов для каждого класса в вашем проекте может или не может быть необходимым.Это полностью дизайнерское решение.Я обнаружил, что в основном это не так.Часто в n-ярусном дизайне вы хотите абстрагировать слой между доступом к данным и логикой.Я бы сказал, что вы должны работать над этим, так как это помогает в тестировании логики без большой инфраструктуры, необходимой для тестов.Методы абстракции, такие как внедрение зависимостей и IoC , потребуют от вас сделать что-то подобное и упростят тестирование указанной логики.

Я бы изучил то, что вы пытаетесь протестировать, и сосредоточился бы на областях, которые вы считаете наиболее подверженными ошибкам.Это может помочь вам решить, нужны ли интерфейсы.

...