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

Идиома pImpl и проверяемость

Идиома pImpl в С++ направлена ​​на скрыть детали реализации (= частные члены) класса от пользователей этого класса. Однако он также скрывает некоторые зависимости этого класса, которые обычно считаются плохими с точки зрения тестирования.

Например, если класс A скрывает детали его реализации в классе AImpl, доступ к которому возможен только из A.cpp, а AImpl зависит от множества других классов, становится очень сложно класс unit test, так как среда тестирования не имеет доступа к методам AImpl, а также не может вводить зависимость в AImpl.

Кто-нибудь сталкивался с этой проблемой раньше? и нашли ли вы решение?

- изменить -

По смежной теме кажется, что люди предлагают проверять публичные методы, открытые интерфейсом, а не внутренними. Хотя я могу концептуально понять это утверждение, я часто обнаруживаю, что мне нужно тестировать частные методы изолированно. Например, когда публичный метод вызывает метод private helper, который содержит некоторую нетривиальную логику.

4b9b3361

Ответ 1

Идея, лежащая в основе pimpl, заключается не столько в том, чтобы скрыть детали реализации из классов (частные члены уже делают это), но и для перемещения деталей реализации из заголовка. Проблема в том, что в модели С++ include, изменение частных методов/переменных заставит любой файл, включая этот файл, перекомпилироваться. Это боль, и почему Pimpl стремится устранить. Это не помогает предотвратить зависимости от внешних библиотек. Другие методы делают это.

Ваши модульные тесты не должны зависеть от реализации класса. Они должны убедиться, что ваш класс действительно действует так, как должен. Единственное, что действительно имеет значение, - это то, как объект взаимодействует с внешним миром. Любое поведение, которое ваши тесты не могут обнаружить, должно быть внутренним для объекта и, следовательно, не имеет значения.

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

Например, предположим, что у меня есть класс, который принимает строку в качестве параметра в свой конструктор. Строка является фактическим немного мини-языка, который указывает некоторые из поведения объекта. (Строка, вероятно, поступает из файла конфигурации или чего-то еще). Теоретически, я должен быть в состоянии проверить разбор этой строки, построив разные объекты и проверив поведение. Но если мини-язык достаточно сложный, это будет сложно. Итак, я определяю другую функцию, которая берет строку и возвращает представление контекста (например, ассоциативный массив или что-то еще). Затем я могу проверить эту функцию разбора отдельно от основного объекта.

Ответ 2

Зачем нужен unit test доступ к внутренним элементам реализации A?

unit test должен тестировать A, и поэтому он должен заботиться только о вводе и выводе A. Если что-то не видно в интерфейсе A (прямо или косвенно), то на самом деле может и не быть частью Aimpl (поскольку его результаты не видны внешнему миру).

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

Ответ 3

Если вы выполняете правильную инъекцию зависимостей, любой класс зависимостей A, который должен быть передан через свой публичный интерфейс, - если ваш pImpl мешает вашему тестированию из-за зависимостей, кажется, что вы не вводите эти зависимости,

Тестирование модуля должно касаться только открытого интерфейса, который подвергается классу A; что A делает внутренне с зависимостями, это не ваша проблема. Пока все вводится должным образом, вы должны иметь возможность проходить в насмешках, не беспокоясь о внутренней реализации. В каком-то смысле вы можете сказать, что тестируемость и правильная pImpl идут рука об руку, поскольку не проверяемая реализация скрывает детали, которые не должны быть скрыты.

Ответ 4

Модульное тестирование должно поставить класс реализации через его шаги. Как только класс PIMPL находится на картинке, вы уже попадаете в область "интеграции" - и, следовательно, U/T не применяется как таковой. PIMPL - это все, что скрывает реализацию - вы не должны знать настройки класса для реализации.

Ответ 5

Идиома pImpl делает тестирование намного проще. Достаточно странно видеть набор ответов на тему "не тестировать реализацию", чтобы мотивировать ответ так долго после OP.

В обычном, не-pimpl на основе С++ у вас есть класс с государственными и частными полями. Публичные поля легко тестировать, частные поля несколько более утомительны. Разделение между публичным и частным важно, хотя, поскольку оно уменьшает ширину api и обычно облегчает последующие изменения.

При использовании этой идиомы доступен лучший вариант. Вы можете иметь точно такой же "открытый" интерфейс, как с одним классом, но теперь есть только одно частное поле, содержащее какой-либо указатель, например

class my_things
{
  public:
    my_things();
    ~my_things();
    void do_something_important(int);
    int also_this();
  private:
    struct my_things_real;
    std::unique_ptr<my_things_real> state;
};

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

void my_things::do_something_important(int x) { state->doit(x); } // etc

class my_things_real // I'd probably write 'struct'
{
  public:
    int value;
    void doit(int x) { value = x; }
    int getit() { return value; }
};

Затем выполняются единичные тесты против реального класса. Испытайте столько или меньше, сколько хотите. Я сознательно назвал его "реальным", а не "им", чтобы гарантировать, что он не ошибается для простой детали реализации.

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

По смутно связанному примечанию мне кажется абсурдным, что многие другие последовательные люди выступают за прохождение модульного тестирования для чего-либо, что не легко доступно через внешний API. Функции самого низкого уровня вряд ли защищены от ошибок программиста. Тестирование для проверки пригодности api является важным и ортогональным для проверки правильности деталей реализации.