Деструкторы тестирования модулей?

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

class X
{
private:
 int *x;
public:
 X()
 {
 x = new int;
 }
 ~X()
 {
 delete x;
 }
 int *getX() {return x;}
 const int *getX() const {return x;}
};

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

7 ответов

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

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

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

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


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

template<class t="">
class X
{
 T *x;
 public:
 X(T* inx)
 : x(inx)
 {
 }
 // etc
};
</class>

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

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


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

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

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


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

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


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

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

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

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


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

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

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


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

licensed under cc by-sa 3.0 with attribution.