Pull to refresh

Comments 18

Жалко, что на хабре нет некого механизма разделения топиков по уровню пользователя, для которого пишется та или иная статья. Можно было бы выставлять: для начинающих, для продвинутых и т.д.
Концепт хороший. Но у вас пример довольно легкий, и больше похож на функциональное тестирование. Т.е. те подсистемы, которые лежат внутри словаря (хранилище данных, какие-то другие самостоятельные объекты) не экранированы. Точно хорошо подойдет для тех мест, где получаются какие-то данные извне.
А что насчет сложности проверки самой целостности? Есть подозрения, что это может быть весьма и весьма нетривиальной задачей в более сложных случаях.
Вообще любопытно, хоть chaos-mockup делай :)
Согалсен, по-моему менее затратно будет доверить такую работу опытному тестировщику.
А подход интересный…
Все-таки движение от искусства, которое под силу только опытным тестировщикам, к технологии, которой можно обучить — правильное. Тогда завтра затраты будут меньше.
При подобном стресс-тестировании, случайные данные обязательно нащупают определённые скрытые пограничные точки, и протестируют те ветки ваших условий, до которых ваши выдуманные тесты при обычных обстоятельствах не доходили.

Ну это только в том случае, если «скрытые пограничные точки» не мельче острова Гренландия. Если пограничные множества маленькие, а размерность входных данных велика, то вероятность сгенерировать множество входны аргументов такое, что оно затронет эту пограничную ошибку, ничтожно мала.
Идея интересная. Чем-то напоминает фаззинг (fuzz testing).
Я работаю как раз в компании, которая пишет софт для тестирования чипов на этапе разработки (пока они в софте).
В качестве методологии мы предлагаем клиентам MDV — Metric Driven Verification.
Т.е. гоняются случайные тесты (вернее тесты не случайные, но со случайным инпутом) и по метрикам таким как Code coverage, Assert coverage, functional coverage и другим (метрик много) принимается решение о готовности продукта.
Задача неправильно поставлена.

> Мы составляем генераторы для получения случайных аргументов I, подаём их на вход компонентам C
> и проверяем выходы O, а также проверяем дополнительными тестами целостность состояния компонента C.

Дело в том, что результатом работы компонента является не только O, но и его измененное внутреннее состояние. O зависит не только от I, но и от состояния компонента на момент подачи ему I.

Сгенерировать I само по себе может быть достаточно сложно (один из предельных примеров — транслятор, для которого надо генерировать корректные входные программы), но ещё и сам компонент надо загнать во все возможные состояния. То есть, тест — это не просто цикл, а обход графа состояний компонента.

Отдельная песня, если взаимодействие с компонентом идет не через вызовы синхронного API, а, например, по сети.

Мы эту собаку не первый год грызем: citforum.ru/SE/testing/unitesk/
www.unitesk.ru

спасибо, ссылка интересная, анализ приведён подробный
именно за проверку изменённого внутреннего состояния и отвечает проверка целостности:
"… а также проверяем дополнительными тестами целостность состояния компонента C..."
Проверить — это ползадачи. Вторая половина — подавать на вход компонента значения надо в разных состояниях компонента. То есть тестами являются цепочки вызовов. Нереально покрыть все классы состояний компонента цепочками случайных вызовов.

А что за дополнительные тесты, откуда они берутся? У вас это черезчур кратко.
Да, в примере разобранном выше состояние объекта было одно — он хранит определённый набор слов и важные операции для доступа дают совпадения с эталоном.

Дополнительные тесты пишутся для каждого компонента:
"… Разумеется на 1-м этапе придётся настроить подсистему контроля целостности, но потом всё пойдёт как по маслу..."

Для частного примера из статьи тест целостности был следующим:

if ([std count] != [sd wordsCount])
{   
 NSLog(@"Words count mismatch %u vs %u",(unsigned)[std count],[sd wordsCount]);
 status = 4;
}   
else
{   
 for (NSString * w in [std allKeys])
 {   
  NSString * entry = [sd lookupWord:w];
  NSString * stdEntry = [std objectForKey:w];
  if (![entry isEqualToString:stdEntry])
  {   
   NSLog(@"Entry mismatch for word '%@', expected '%@'",w,stdEntry);
   status = 5;
   break;
  }   
  unsigned wi = [sd indexForWord:w];
  if (wi == TIDNotFound)
  {   
   NSLog(@"Index-word error '%@'",w);
   status = 6;
   break;
  }   
  NSString * iw = [sd wordByIndex:wi];
  if (!iw)
  {   
   NSLog(@"Word-index error for index %u",wi);
   status = 7;
   break;
  }   
  if (![iw isEqualToString:w])
  {   
   NSLog(@"Mismatch '%@' vs '%@' at index %u",w,iw,wi);
   status = 8;
   break;
  }   
 }   
}


Пример конечно не сложный, для произвольного случая можно дать лишь общие рекомендации — как Вы и говорите — постараться пройтись по всем состояниям и произвести проверки. И постараться подобрать генераторы, которые прогонят систему по всем состояниям… Но это палка о двух концах — в погоне за всем и при излишней детерминированности входных параметров могут появиться случайные ограничения и мы попадём в иной класс ошибок. Вероятностная система очень чувствительна к входным параметрам. Оставляя место полному хаусу мы можем получить неожиданные результаты, а при попытке что-то фиксировать — мы теряем потеряем возможность что-то измерить — аналогия с принципом неопределённости Гейзенберга.
Chaos-driven? Тзинч уже потирает руки.
логика простая — если Хаос породил ошибки
то сам же Хаос и поможет их обнаружить.
клин клином.
Как говорится: пользователь — это периферийное устройство хаотичного ввода.

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

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

Точно установить причину не удалось, а вероятностный тест помог не только найти проблему, но и минимизировать путь к воспроизводству ошибки. И устранить 4 наведённые проблемы, после исправления 1-й логической ошибки. Таким образом удалось исправить одну корневую и 4 наведённых ошибки во вспомогательных методах, в результате устранения 1-й. И причём весьма чёткие траектории получились.
Например выпадает ошибка на 1000-м проходе, сохраняем, прогоняем стресс тест ещё раз — выпадает на 249-м, не успокаеваемся и снова — 128, ну где-то в среднем от 100 итераций получается в этом случае.
Начинаем упрощать — выкидывать операции пока ошибка воспроизводится по журналу операций.
Удавалось сократить журнал до 10-20 операций.
Дальше в ход шёл двоичный анализ дампов с подтверждением целостности после каждой операции согласно документированному двоичному формату.
Удавалось найти артефакт и точку программы, которая воспроизвела на его.
Ну это в тему, что лишние тесты никогда не бывают лишними.
(это утверждение тоже не бесспорно, т.к. порой роль играет и время прогона тестов и трудозатраты на их поддержку).

А можно ли для этого подхода определить критерии достаточности? Достаточное количество итераций, например.

Просто без подобных критериев, данный метод напоминает тыканье пальцем в небо «авось упадет».

В случае с обычными тестами, хотя бы можно определить когда достигнуто 100% покрытие, т.е. все ветвления пройдены со всеми наборами значений.

А с данным подходом, когда и как Вы решаете, что код протестирован достаточно хорошо?

И правильно ли я понимаю, что Вы предлагаете использовать данный подход для тестирования системы в целом? Т.е. что то вроде интеграционных тестов черного ящика?

PS
В тексте есть опечатка — слово «длинны».
Sign up to leave a comment.

Articles