Для модульного тестирования требуется много интерфейсов? - PullRequest
1 голос
/ 10 июля 2020

Сейчас я работаю над своим первым проектом, который использует TDD и модульное тестирование. Я пишу на C ++ с библиотеками googletest и googlemock . Для проекта требуется множество небольших классов с определенными функциями. Я в основном использую внедрение зависимостей в конструкторах. В соответствии с этим:

Когда следует издеваться? и Когда использовать макетные объекты в модульных тестах

зависимость, введенная в конструктор, является хорошим кандидатом для издевательство в модульном тестировании. В C ++ есть два способа сделать это:

  1. Создание интерфейса (чистый виртуальный класс). Производственный класс и фиктивный класс являются производными от него.
  2. Создание виртуальных функций-членов внутри производственного класса. Мок-класс происходит от производственного класса. Проблема возникает, когда производственный класс не определяет конструктор по умолчанию. Мок-класс должен явно вызывать конструктор базового класса (лично это не то, что должен делать фиктивный объект).

Как вы справляетесь с подобной ситуацией? Вы предпочитаете много интерфейсов (для N классов требуется N интерфейсов) или производных от базового класса?

1 Ответ

3 голосов
/ 10 июля 2020

На подобный вопрос нет единственно правильного ответа, но я могу поделиться некоторым опытом моей команды. Мы используем следующий подход. Проект разделен на то, что мы внутренне называем «модулями». В основном это просто классы C ++, но их особенность заключается в том, что это самые большие / самые важные классы в проекте. Интерфейсы этих классов доступны другим модулям (это настраивается через CMake), поэтому их можно использовать друг с другом. Каждый модуль представляет собой небольшую библиотеку stati c, и внутри этой библиотеки есть дополнительные классы C ++, которые необходимы только для выполнения функциональности содержащего модуля, но не имеют отношения к другим модулям. Они настроены как частные внутри модуля (снова через CMake), и другие модули не знают об этих классах.

Итак, после краткого объяснения того, как устроен наш проект, вернемся к вашему вопросу:

  1. Для основных классов модуля (обычно 1-3), которые должны быть представлены другим модулям, мы создаем интерфейс (чистый виртуальный класс) с подробной документацией (точный контракт, подробно описывающий, как клиент должен использовать классы, диаграммы последовательностей, подробную документацию по всем методам и т. д. c.). Затем у нас есть макет, который наследуется от этого интерфейса и предоставляется другим модулям, а также класс реализации (то, что вы называете производственным классом), который также наследуется, но является частным внутри модуля (другим классам не нужны детали реализации, только знание того, как создавать и использовать экземпляр интерфейса).

  2. Для других классов, которые являются частными внутри каждого модуля, мы просто создаем виртуальные методы внутри производственного класса, чтобы упростить работу, а их mocks просто наследуется от класса Production. Кстати, ваша проблема с конструктором по умолчанию должна быть легко решена путем объявления защищенного конструктора по умолчанию в классе Production?

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

...