Простая идиома и тестируемость - PullRequest
17 голосов
/ 07 мая 2010

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

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

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

- редактировать -

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

Ответы [ 5 ]

17 голосов
/ 07 мая 2010

Почему модульному тестированию необходим доступ к внутренним компонентам реализации A?

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

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

16 голосов
/ 07 мая 2010

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

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

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

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

11 голосов
/ 07 мая 2010

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

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

4 голосов
/ 03 декабря 2015

Идиома pImpl значительно упрощает тестирование. Достаточно странно видеть набор ответов на тему «не проверяйте реализацию», чтобы мотивировать ответы так долго после ОП.

В обычном C ++, не основанном на 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. Функции самого низкого уровня вряд ли защищены от ошибок программиста. Проверка того, что API пригоден для использования, важна и ортогональна для проверки правильности деталей реализации.

0 голосов
/ 04 февраля 2015

В модульном тестировании класс реализации должен пройти свои темпы. Как только класс PIMPL находится на рисунке, вы уже находитесь в сфере «интеграции» - и, следовательно, U / T не применяется как таковой. PIMPL полностью скрывает реализацию - вы не должны знать настройки класса реализации.

...