Подтвердить что ты не робот

Тестирование модулей: кодирование на интерфейсы?

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

Есть две линии мысли, которые я нашел, исследуя это:

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

  • Единичные тесты шлейфов /mocks часто это "другая реализация", и поэтому да, вы должны создать intefaces.

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

4b9b3361

Ответ 1

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

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_;
};

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

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

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 (класс, встроенный в мою библиотеку), чтобы я мог unit test вещь. Я бы предпочел не создавать интерфейсы и тестировать два класса вместе (третий вариант, который я дал).

Ответ 2

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

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

  • "white-box" тестирует подробные сведения о реализации, которые ДОЛЖЕН вообще быть доступны извне. Такое тестирование подтвердит, что компоненты реализации работают по назначению. Таким образом, их результаты в основном представляют интерес для разработчиков, пытающихся выяснить, что сломано, или нуждается в mantainance.

mocks по их определению вписываются в модульные архитектуры, но из этого не следует, что все классы в проекте должны быть полностью модульными. Его прекрасно можно нарисовать, когда группа классов узнает друг о друге. Они, как группа, могут представлять другие модули из перфектива некоторого класса интерфейса фасада. Тем не менее, вы по-прежнему хотите иметь драйверы теста белого ящика внутри этого модуля с информацией о деталях реализации. Следовательно, такое тестирование не подходит для насмешек.

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

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

Ответ 3

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

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