Модульное тестирование деструкторов? - PullRequest
14 голосов
/ 23 ноября 2008

Есть ли хороший способ деструкторов модульного тестирования? Например, скажем, у меня есть такой класс (надуманный) пример:

class X
{
private:
    int *x;

public:
    X()
    {
         x = new int;
    }

    ~X()
    {
         delete x;
    }

    int *getX() {return x;}
    const int *getX() const {return x;}
};

Есть ли хороший способ провести модульное тестирование, чтобы убедиться, что x удаляется без засорения моего hpp-файла тестами #ifdef или нарушения инкапсуляции? Основная проблема, с которой я сталкиваюсь, заключается в том, что трудно сказать, действительно ли x удалено, особенно потому, что объект находится вне области действия во время вызова деструктора.

Ответы [ 7 ]

8 голосов
/ 23 ноября 2008

Может быть, что-то сказать о внедрении зависимости. Вместо создания объекта (в данном случае int, но в непонятном случае, скорее всего, пользовательского типа) в его конструкторе, объект передается в качестве параметра конструктору. Если объект создается позже, то фабрика передается в конструктор X.

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

Конечно, вы не можете смоделировать (или иным образом заменить) встроенный тип, поэтому в данном конкретном случае это бесполезно, но если вы определите объект / фабрику с помощью интерфейса, тогда вы можете.

Проверка на утечки памяти в модульных тестах часто может быть выполнена на более высоком уровне, как говорили другие. Но это только проверяет, что был вызван деструктор a , но это не доказывает, что был вызван деструктор right . Так что это не будет, например. поймать отсутствующее «виртуальное» объявление в деструкторе типа члена x (опять же, не имеет значения, если это просто int).

5 голосов
/ 23 ноября 2008

Я думаю, что ваша проблема в том, что ваш текущий пример не поддается тестированию. Поскольку вы хотите знать, был ли удален x, вам действительно нужно заменить x на макет. Это, вероятно, немного OTT для int, но я думаю, что в вашем реальном примере у вас есть какой-то другой класс. Чтобы сделать его тестируемым, конструктор X должен запросить объект, реализующий интерфейс int:

template<class T>
class X
{
  T *x;
  public:
  X(T* inx)
    : x(inx)
  {
  }

  // etc
};

Теперь просто смоделировать значение для x, и этот макет может обрабатывать проверку на правильное уничтожение.

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

2 голосов
/ 23 ноября 2008

Я склонен придерживаться подхода «любыми средствами» к тестированию. Если ему нужен тест, я готов пропустить абстракции, нарушить инкапсуляцию и взломать ... потому что тестированный код лучше, чем красивый код. Я буду часто называть методы, которые разбивают это, что-то вроде VaildateForTesting или OverrideForTesting, чтобы прояснить, что нарушение инкапсуляции предназначено только для тестирования.

Я не знаю другого способа сделать это в C ++, кроме вызова деструктора в синглтон, чтобы зарегистрировать, что он был уничтожен. Я придумал метод для выполнения чего-то похожего на это в C # с использованием слабой ссылки (я не нарушаю инкапсуляцию или абстракции с этим подходом). Я не достаточно изобретателен, чтобы придумать аналогию с C ++, но ВЫ можете. Если это поможет, отлично, если нет, извините.

http://houseofbilz.com/archive/2008/11/11/writing-tests-to-catch-memory-leaks-in-.net.aspx

1 голос
/ 27 августа 2012

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

Предполагая, что память ограничена, вы можете попробовать этот метод:

  1. Выделите память до тех пор, пока не произойдет сбой выделения с сообщением о нехватке памяти (перед выполнением любого соответствующего теста для деструктора), и сохраните размер памяти, доступной до запуска теста.
  2. Запустите тест (вызовите конструктор и выполните некоторые действия по вашему желанию для нового экземпляра).
  3. Запустите деструктор.
  4. Запустите часть распределения снова (как в шаге 1) если вы можете выделить точно такую ​​же память, какую вам удалось выделить до запуска теста, деструктор работает нормально.

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

1 голос
/ 23 ноября 2008

В этом примере определите и оснастите свои собственные глобальные новые и удалите.

Чтобы избежать #ifdefs, я подружился с тестовыми классами. Вы можете установить / сохранить / получить состояние по необходимости для проверки результатов вызова.

0 голосов
/ 23 ноября 2008

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

0 голосов
/ 23 ноября 2008

Некоторые компиляторы перезаписывают удаленную память известным шаблоном в режиме отладки, чтобы помочь обнаружить доступ к висящим указателям. Я знаю, что Visual C ++ использовал 0xDD, но некоторое время не использовал его.

В вашем тестовом примере вы можете сохранить копию x, вывести ее из области видимости и убедиться, что * x == 0xDDDDDDDD:

void testDestructor()
{
    int *my_x;
    {
        X object;
        my_x = object.getX();
    }
    CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}
...