Как бы вы протестировали распределитель памяти? - PullRequest
7 голосов
/ 23 сентября 2008

Сегодня много людей, которые продают юнит-тестирование, как хлеб развития. Это может даже работать для сильно алгоритмически ориентированных подпрограмм. Тем не менее, как бы вы протестировали модуль, например, распределитель памяти (подумайте malloc () / realloc () / free ()). Нетрудно создать работающий (но абсолютно бесполезный) распределитель памяти, который удовлетворяет указанному интерфейсу. Но как обеспечить надлежащий контекст для функциональности модульного тестирования, которая абсолютно желательна, но не является частью контракта: объединение свободных блоков, повторное использование свободных блоков при следующих выделениях, возврат избыточной свободной памяти в систему, утверждение политики выделения (например, в первую очередь) действительно уважается и т. д.

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

Есть мысли?

Ответы [ 8 ]

11 голосов
/ 23 сентября 2008

Код с высокой степенью тестирования имеет тенденцию структурироваться иначе, чем другой код.

Вы описываете несколько задач, которые должен выполнять распределитель:

  • объединение свободных блоков
  • повторное использование свободных блоков на следующем Распределение
  • возврат избыточной свободной памяти система
  • утверждая, что политика выделения (например, в первую очередь) действительно уважают

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

Далее, я бы сказал, что в любом случае автоматическое тестирование любого рода лучше, чем отсутствие автоматического тестирования. Я определенно сосредоточился бы больше на том, чтобы убедиться, что ваши тесты делают что-то полезное, чем на том, чтобы беспокоиться о том, правильно ли вы использовали макеты, гарантировали ли вы, что он должным образом изолирован и действительно ли это модульный тест. Это все замечательные цели, которые, надеюсь, сделают 99% тестов лучше. С другой стороны, пожалуйста, используйте здравый смысл и свое лучшее инженерное решение, чтобы выполнить работу.

Без примеров кода я не думаю, что могу быть более конкретным.

10 голосов
/ 23 сентября 2008

Если там есть какая-либо логика, она может быть проверена модулем.
Если ваша логика включает принятие решений и вызов API-интерфейсов ОС / оборудования / системы, имитируйте / макетируйте зависимые от устройства вызовы и тестируйте свою логику, чтобы убедиться, что правильные решения принимаются при заданном наборе предварительных условий. Следуйте триаде Arrange-Act-Assert в своем модульном тесте.
Утверждения не являются заменой для автоматических юнит-тестов. Они не сообщают вам, какой сценарий не удался, они не обеспечивают обратную связь во время разработки, их нельзя использовать, чтобы доказать, что код отвечает всем спецификациям.

Неопределенное обновление: Я не знаю точные вызовы методов .. Я думаю, что я «сверну свои» Допустим, ваш код проверяет текущие условия, принимает решение и при необходимости обращается к ОС. Допустим, ваши вызовы ОС (у вас их может быть гораздо больше):

void* AllocateMemory(int size); 
bool FreeMemory(void* handle);
int MemoryAvailable();

Сначала превратите это в интерфейс, I_OS_MemoryFacade. Создайте реализацию этого интерфейса для фактических вызовов ОС. Теперь заставьте свой код использовать этот интерфейс - теперь вы отсоединили свой код / ​​логику от устройства / ОС. Далее в модульном тесте вы используете фиктивную среду (ее цель - дать вам фиктивную реализацию указанного интерфейса. Затем вы можете сказать фиктивной среде, что ожидает выполнения этих вызовов с этими параметрами и верните его, когда они будут сделаны. В конце теста вы можете попросить фиктивную платформу проверить, все ли ожидания были удовлетворены. (Например, в этом тесте AllocateMemory следует трижды вызывать с 10, 30, 50 как последующими параметрами 3 вызовами FreeMemory. Проверьте, возвращает ли MemoryAvailable начальное значение.)
Поскольку ваш код зависит от интерфейса, он не знает разницы между реальной реализацией и реализацией поддельной / ложной, которую вы используете для тестирования. Для получения дополнительной информации об этом Google выложите «фиктивные рамки».

1 голос
/ 09 апреля 2009

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

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

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

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

Вы также можете включить тестирование производительности, стресс-тестирование и т. Д. Они не будут модульными тестами, потому что они будут проверять все, но они очень полезны в случае выделения памяти.

Юнит-тестирование не исключает такого рода тестов. Лучше иметь оба из них.

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

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

0 голосов
/ 16 июня 2015

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

prefix_malloc();
prefix_free();

, а затем

#ifndef USE_PREFIX
#define prefix_malloc malloc
#define prefix_free free
#endif

Теперь настройте систему сборки на компиляцию версии библиотеки с использованием -DUSE_PREFIX. Напишите свои модульные тесты для вызова prefix_malloc и prefix_free. Это позволяет отделить состояние вашего распределителя от состояния системного распределителя.

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

0 голосов
/ 24 ноября 2009

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

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

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

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

Лично я нахожу большинство юнит-тестов скорее чужим желанием, чем моим. Я думаю, что любой модульный тест должен быть написан как обычная программа, за исключением того факта, что он не выполняет ничего, кроме тестирования библиотеки / алгоритма или какой-либо части кода.

В моих модульных тестах обычно не используются такие инструменты, как CUnit , CppUnit и аналогичное программное обеспечение. Я создаю свои собственные тесты. Например, недавно мне нужно было протестировать новую реализацию контейнера в обычных случаях на утечки памяти. Модульный тест оказался недостаточно полезным для обеспечения хорошего теста. Вместо этого я создал свой собственный распределитель и заставил его не выделять память после определенного (настраиваемого) количества выделений, чтобы увидеть, есть ли в моем случае утечки памяти в этих случаях (и это было :)).

Как это можно сделать с помощью модульного теста? С большим усилием, чтобы ваш код вписался в «шаблон» модульного теста.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...