Я пишу (рекурсивный) код, который перемещается по графу зависимостей, ищет циклы или противоречия в зависимостях. Тем не менее, я не уверен, как подойти к юнит-тестированию. Проблема состоит в том, что одна из наших основных задач заключается в том, будет ли код обрабатывать все интересные структуры графов, которые могут возникнуть, и обеспечивать правильную обработку всех узлов.
Хотя обычно 100% покрытия строк или ветвей достаточно, чтобы быть уверенным в том, что какой-то код работает, создается впечатление, что даже при 100% покрытии пути у вас все еще есть сомнения.
Итак, как можно выбрать структуры графов для тестовых случаев, чтобы иметь уверенность в том, что их код может обрабатывать все мыслимые перестановки, которые вы найдете в данных реального мира.
PS - Если это имеет значение, все ребра в моем графе помечены "должны иметь" xor "не может иметь", и нет никаких тривиальных циклов, и между любыми двумя узлами есть только одно ребро.
PPS- Это дополнительное постановление проблемы было первоначально опубликовано автором вопроса в комментарии ниже:
For all vertices N in forest F, for all vertices M, in F, such that if there are any walks between N and M they all must either use only edges labelled 'conflict' or 'requires'.
Ответы:
Это звучит как хорошее начало. Я предполагаю, что вы уже пытались применить некоторые классические методы, такие как анализ граничных значений или разделение эквивалентности , как вы уже упоминали тестирование на основе покрытия. После того, как вы потратили много времени на создание хороших тестовых примеров, вы придете к тому, что у вас, вашей команды, а также ваших тестеров (если они у вас есть) не хватит идей. И это тот момент, когда вы должны покинуть путь модульного тестирования и начать тестирование с как можно большим количеством реальных данных.
Должно быть очевидно, что вы должны попытаться выбрать большое разнообразие графиков из ваших производственных данных. Возможно, вам придется написать некоторые дополнительные инструменты или программы только для этой части процесса. Сложная часть здесь, вероятно, состоит в том, чтобы проверить правильность вывода ваших программ. Когда вы поместите в свою программу десять тысяч различных графов реального мира, как вы узнаете, всегда ли ваша программа выдает правильные результаты? Очевидно, что вы не можете проверить, чем вручную. Поэтому, если вам повезет, вы сможете сделать вторую, очень простую реализацию вашей проверки зависимостей, которая может не соответствовать вашим ожиданиям производительности, но ее легче проверить, чем ваш первоначальный алгоритм. Вам также следует попытаться интегрировать множество проверок правдоподобия непосредственно в вашу программу (например,
Наконец, научитесь принимать, что каждый тест может только доказать наличие ошибок, но не отсутствие ошибок.
источник
1. Генерация рандомизированных тестов
Напишите алгоритм, который генерирует графики, сгенерируйте несколько сотен (или более) случайных графиков и бросьте каждый на ваш алгоритм.
Сохраните случайное начальное число графиков, которые вызывают интересные сбои, и добавьте их в качестве модульных тестов.
2. Жесткий код хитрых частей
Некоторые структуры графиков, которые вы знаете, хитры, вы можете сразу же написать код или написать код, который объединяет их и подталкивает их к вашему алгоритму.
3. Создайте исчерпывающий список
Но если вы хотите быть уверены, что «код может обрабатывать все мыслимые перестановки, которые вы найдете в данных реального мира», вам нужно генерировать эти данные не из случайного начального числа, а путем обхода всех перестановок. (Это делается при тестировании железнодорожных сигнальных систем метрополитена и дает вам огромное количество случаев, которые требуются для проверки возрастом. Для станций метро система ограничена, поэтому существует верхний предел для числа перестановок. Не уверен, как ваш случай применяется)
источник
For all vertices N in forest F, for all vertices M, in F, such that if there are any walks between N and M they all must either use only edges labelled 'conflict' or 'requires'.
домен не является проблемой.Никакого тестирования в этом случае будет недостаточно, даже тонны реальных данных или размытости. 100% покрытия кода или даже 100% покрытия пути недостаточно для тестирования рекурсивных функций.
Либо рекурсивная функция выдерживает формальное доказательство (в данном случае это не должно быть так сложно), либо нет. Если код слишком переплетен с кодом приложения, чтобы исключить побочные эффекты, вот с чего начать.
Сам алгоритм звучит как простой алгоритм затопления, подобный простому широкому первому поиску, с добавлением черного списка, который не должен пересекаться со списком посещенных узлов, запускаемых со всех узлов.
Этот итеративный алгоритм удовлетворяет условию, что никакая зависимость не может требоваться и помещаться в черный список одновременно для графов произвольной структуры, начиная с любого произвольного артефакта, в силу чего всегда требуется начальный артефакт.
Хотя он может быть или не быть таким же быстрым, как ваша собственная реализация, можно доказать, что он завершается для всех случаев (поскольку для каждой итерации внешнего цикла каждый элемент может быть помещен в
tovisit
очередь только один раз ), он затопляет всю достижимую граф (индуктивное доказательство), и он обнаруживает все случаи, когда требуется артефакт и черный список одновременно, начиная с каждого узла.Если вы можете показать, что ваша собственная реализация имеет те же характеристики, вы можете доказать правильность, не приводя к модульному тестированию. Только основные методы для извлечения и извлечения из очередей, подсчета длины очереди, итерации по свойствам и т. Д. Должны быть проверены и показаны без побочных эффектов.
РЕДАКТИРОВАТЬ: То, что этот алгоритм не доказывает, ваш график свободен от циклов. Направленные ациклические графы - это хорошо изученная тема, поэтому поиск готового алгоритма для доказательства этого свойства также должен быть легким.
Как видите, нет необходимости изобретать велосипед вообще.
источник
Вы используете такие фразы, как «все интересные графовые структуры» и «обрабатываются соответствующим образом». Если у вас нет способов проверить свой код на соответствие всем этим структурам и определить, правильно ли код обрабатывает график, вы можете использовать только такие инструменты, как анализ покрытия теста.
Я предлагаю вам начать с поиска и тестирования с несколькими интересными графовыми структурами и определить, какой будет подходящая обработка, и увидеть, что код делает это. Затем вы можете начать возмущать эти графы в а) сломанные графы, которые нарушают правила, или б) не очень интересные графы, которые имеют проблемы; Посмотрите, правильно ли ваш код не справляется с ними.
источник
Вы можете попробовать выполнить топологическую сортировку и посмотреть, удастся ли это. Если это не так, то у вас есть хотя бы один цикл.
источник
Когда дело доходит до такого сложного для тестирования алгоритма, я бы выбрал TDD, где вы строите алгоритм на основе тестов,
Короче говоря,
и повторите цикл,
В этой конкретной ситуации,
Одним из важных аспектов в этом методе является необходимость всегда добавлять тест для возможного шага (где вы разбиваете возможные сценарии на простые тесты), и когда вы охватываете все возможные сценарии, обычно алгоритм развивается автоматически.
Наконец, вам нужно добавить один или несколько сложных интеграционных тестов, чтобы увидеть, есть ли непредвиденные проблемы (такие как ошибки переполнения стека / ошибки производительности, когда ваш график очень большой и когда вы используете рекурсию)
источник
Мое понимание проблемы, как первоначально заявлено, а затем дополнено комментариями в ответе Маке, включает следующее: 1) оба типа ребер (зависимости и конфликты) направлены; 2) если два узла соединены одним ребром, они не должны быть соединены другим, даже если он другого типа или наоборот; 3) если путь между двумя узлами можно построить путем смешивания ребер разных типов, то это скорее ошибка, чем игнорируемое обстоятельство; 4) Если существует путь между двумя узлами, использующими ребра одного типа, тогда между ними не может быть другого пути, использующего ребра другого типа; 5) циклы одного типа ребер или смешанных типов ребер недопустимы (из предположения приложения я не уверен, что циклы только конфликта являются ошибкой, но это условие может быть удалено, если нет).
Кроме того, я предполагаю, что используемая структура данных не предотвращает нарушения этих требований (например, условие 2 нарушения графа не может быть выражено в карте от пары узлов до (тип, направление), если пара узлов всегда сначала имеет наименее пронумерованный узел.) Если определенные ошибки не могут быть выражены, это уменьшает количество рассматриваемых случаев.
Здесь на самом деле можно рассмотреть три графика: два исключительно одного типа ребер и смешанный граф, образованный объединением одного из каждого из двух типов. Вы можете использовать это, чтобы систематически генерировать все графики вплоть до некоторого количества узлов. Сначала сгенерируйте все возможные графы из N узлов, имеющих не более одного ребра между любыми двумя упорядоченными парами узлов (упорядоченные пары, потому что это ориентированные графы.) Теперь возьмем все возможные пары этих графов, один из которых представляет зависимости, а другой представляет конфликты, и сформировать объединение каждой пары.
Если ваша структура данных не может выразить нарушения условия 2, вы можете значительно сократить число рассматриваемых случаев, только построив все возможные графы конфликтов, которые помещаются в пространства графов зависимостей, или наоборот. В противном случае вы можете обнаружить нарушения условия 2 при формировании объединения.
При обходе объединенного графа в ширину от первого узла вы можете создать набор всех путей к каждому достижимому узлу, и при этом вы можете проверить наличие всех условий (для обнаружения цикла вы можете использовать алгоритм Тарьяна .)
Вам нужно только рассмотреть пути от первого узла, даже если граф отключен, потому что пути от любого другого узла будут отображаться как пути от первого узла в другом случае.
Если пути смешанных ребер можно просто игнорировать, а не ошибки (условие 3), достаточно рассмотреть графы зависимостей и конфликтов независимо и проверить, что если узел доступен в одном, то нет в другом.
Если вы помните пути, найденные при проверке графов N-1 узлов, вы можете использовать их в качестве отправной точки для генерации и оценки графов из N узлов.
Это не создает несколько ребер одного типа между узлами, но это может быть расширено для этого. Однако это значительно увеличило бы число случаев, поэтому было бы лучше, если бы тестируемый код делал невозможным представление или невозможность этого отфильтровать все такие случаи заранее.
Ключ к написанию оракула, как это, состоит в том, чтобы сделать его как можно более простым, даже если это означает, что он неэффективен, так что вы можете установить доверие к нему (в идеале с помощью аргументов в пользу его правильности, подкрепленных тестированием).
Если у вас есть средства для генерации тестовых случаев, и вы доверяете созданному оракулу для точного отделения хорошего от плохого, вы можете использовать его для автоматического тестирования целевого кода. Если это невозможно, ваш следующий лучший вариант - прочесать результаты для конкретных случаев. Оракул может классифицировать найденные ошибки и предоставить вам некоторую информацию о принятых случаях, таких как количество и длина путей каждого типа, а также наличие каких-либо узлов, которые находятся в начале обоих типов путей, и это может помочь вам найти случаи, которые вы не видели раньше.
источник