Manifold Geometry // Многообразная Геометрия

Geometric modeling

Подписаться на эту рубрику по RSS

Заметки на геометрическую тематику.

Тестирование при помощи Draw

«Ведь тот факт, что он в состоянии видеть мыслимое, придает в его глазах науке, которой он занимается, особенную ценность и прелесть» (Феликс Клейн).
Вы можете начать ознакомление с тестовой системой Open CASCADE Technology (OCCT), просмотрев официальную документацию библиотеки.

Преамбула

Тестирование геометрических алгоритмов — задача творческая. Равно как сама геометрия обладает аналитической и синтетической сторонами, тестирование может задействовать ту из них, которая наиболее плодотворна для данной конкретной задачи. Под синтетической геометрией следует понимать геометрию образов, чертеж, проясняющий дело. Аналитическая же геометрия — это слепая формула, выражение чертежа в алгебраической форме. Еще Якоби высказал абсолютно справедливую истину, что геометрические и аналитические принципы — это одно и то же. Но алгебра без наглядного образа становится сухой и лишает нас того, что, по мнению Клейна, и составляет основной кайф геометра — «радости созерцания образа». Аналитический же метод, хотя и является единственно плодотворным, но он, по словам того же Клейна, «легко может привести к соблазну упустить из виду подлинный объект геометрии — фигуру и построение».

Водораздел между двумя подходами к тестированию геометрии проходит там же, где находится граница между научной визуализацией и вычислительной геометрией. Основной момент, который следует принять с самого начала, состоит в том, что заниматься геометрическим моделированием можно не имея сколько-нибудь развитых средств визуализации. Вспомните хотя бы систему SKETCHPAD — родоначальницу современных CAD-систем, где не могло быть и речи о таких приятных техниках отрисовки, как ray tracing и даже shaded view. При должном уровне нигилизма можно прийти даже к тому, что геометрическое моделирование вообще не требует никакой визуализации. Действительно, CAD-модель — это композиция формул и структур данных, вкупе с вычислительными методами для их обработки. Она, если позволите, математически совершенна. Правда, и абсолютно бесполезна в этом состоянии аналитической нирваны.

Чисто аналитическому тестированию мы отдали должное в заметке о легковесном подходе к юнит-тестам. Там изложен довольно общий взгляд на проблему: ничего лишнего, просто и эффективно. Сегодня фокус нашего внимания будет смещен в синтетическую сторону. Отметим сразу, что чисто синтетический подход — это, как правило, плохая идея. По крайней мере, в области геометрического моделирования. Упрощенно можно сказать, что чисто синтетический подход — это сравнение полученного чертежа с ожидаемым. То есть речь идет о сравнении картинок. Такой метод годится для задач визуализации, где непосредственно изображаемый результат является целью работы алгоритма. И хотя для вычислительной геометрии тоже важно иметь графическое представление результатов моделирования, полагаться на визуальный образ нельзя.

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

  • существование результирующих кривых;
  • их непрерывность;
  • их параметрические свойства (скажем, степень и количество узлов сплайна).

Тестирование в Open CASCADE Technology

Draw Test Harness — это консольное приложение (с элементами GUI), предназначенное для работы с API библиотеки Open CASCADE Technology (OCCT). Помимо этого, Draw является также инструментом нерегрессионного тестирования, реализованного в библиотеке. Эта последняя функция Draw представляет для нас особый интерес, так как она может быть переадресована с OCCT на нашу собственную функциональность. Но прежде чем заняться адаптацией Draw под наши нужды, посмотрим как это работает в оригинальной поставке OCCT.

В современных версиях OCCT для сборки библиотеки используется CMake. В этой заметке мы не будем останавливаться на процедуре компиляции, так как эта тема требует отдельного разговора. Укажем только, что для правильного подключения тестирования в OCCT требуется отметить некоторые специфические опции сборки:

  • использование Freeimage;
  • использование Tcl/Tk;
  • включение флага INSTALL_OCCT_TEST_CASES.

Библиотека Freeimage нужна для подготовки скриншотов, которые будут фигурировать в отчетах по тестированию. Флаг INSTALL_OCCT_TEST_CASES указывает, что тестовая база библиотеки (в виде сценариев на языке Tcl) должна быть проинсталлирована вместе с OCCT в директорию, указанную в переменной INSTALL_DIR. Когда конфигурация CMake будет завершена, переходим непосредственно к сборке. Она осуществляется при помощи одной из популярных IDE (MS Visual Studio, Code::Blocks, Eclipse) или через Make-файлы. По завершении сборки требуется выполнить инсталляцию в целевую директорию сборкой спец-проекта INSTALL.

Перейдем теперь в директорию, выбранную нами в качестве значения CMake-переменной INSTALL_DIR. Вся доступная тестовая база OCCT оформлена в виде Tcl-скриптов, помещенных в директорию tests.

Уровнем ниже находятся библиотеки тестов, называемые "test grids", т.е. сетки тестов. Эти сетки образованы по функциональному признаку, что легко видеть из названий соответствующих директорий. Лежащий рядом файл "parse.rules" содержит лексемы для анализа того текста, что оказывается в std::out после прогонки каждого тестового сценария. Здесь указано, в частности, что такие слова как "error" и "exception" должны трактоваться как безусловный провал теста.

Обратимся для примера к сетке "boolean", содержащей тесты для булевых операций. Здесь мы обнаруживаем поддиректории, отвечающие группам тестов. Таким образом в OCCT реализована детализация по функциональному признаку, ведь тестов, как и алгоритмов, в библиотеке очень много. Отметим Tcl-сценарии со специальными именами "begin" и "end". Это "инициализатор" и "терминатор", выполняемые Draw до и после старта тестов из текущей сетки. Файл "grids.list" содержит список всех групп, подлежащих исполнению. Удаляя и добавляя строчки в этот файл, вы можете включать и исключать конкретные группы тестов. Это бывает полезно, если из всей обширной тестовой базы вас интересует лишь некоторая конкретная тестовая ветка, отвечающая тому или иному алгоритму (либо набору входных данных, в зависимости от того, по какому принципу тесты были сгруппированы). Уже знакомый нам файл "parse.rules" выполняет ту же функцию, что и его аналог уровнем выше: он отмечает ключевые слова для вынесения вердикта по каждому тесту («пройден» или «провален»).

Заглянув внутрь директории какой-либо группы, мы обнаружим все имеющиеся тестовые сценарии. Обратите внимание на выбранный способ именования тестов. Первой идет буква, затем — цифра. Придерживаться этого соглашения не обязательно, но это рекомендуется делать, так как в противном случае результирующий HTML-отчет будет плохо структурирован. Обратим также внимание на наличие инициализатора "begin" и терминатора "end" в каждой тестовой группе. Они локальны для текущей группы.

Переходим непосредственно к запуску. Для этого стартуем Draw (используя "draw.bat" под Windows и "draw.sh" под Linux) и выполняем команду "testgrid". Тестирование начинается.

Тестирование осуществляется параллельно путем запуска нескольких процессов DRAWEXE, чтобы максимально задействовать вычислительный ресурс вашей машины. Аппетит Draw можно умерить, указав желательное число задействованных ядер:

testgrid -parallel 4

Некоторые тесты являются самодостаточными ("self-contained"), т.е. не требуют входных данных, читаемых из стороннего файла. Однако подавляющее большинство сценариев опираются на CAD-модели, либо геометрические описания, приходящие извне. Эти тестовые данные не входят в дистрибутив OCCT, и по состоянию на сегодняшний день (ноябрь, 2015г.) недоступны для скачивания. Поэтому в нашем эксперименте мы вынуждены удовлетвориться тем, что только самодостаточные тесты пройдут успешно, тогда как все прочие будут пропущены (т.е. выполнятся с результатом "SKIPPED").

Результаты тестирования доступны в директории "results". Здесь мы обнаружим отчеты и детальные логи по каждому тесту, а также по всей их совокупности (файл "summary.html").

Тестирование стороннего приложения

Принцип

Посмотрев на Draw в действии, вы наверняка захотите подключить такой же механизм тестирования к своему проекту. Желание разумное. И его довольно просто осуществить. Будем работать на платформе Windows 7, MS Visual Studio 2010.

Open CASCADE Technology — кроссплатформенная библиотека, поэтому выбор операционной системы и IDE остается на ваше усмотрение.

Начнем с описания принципа. Взглянув на содержимое произвольного теста из поставки OCCT, вы легко обнаружите синтаксис языка Tcl, расширенный специфическими командами библиотеки. Вот пример простейшего теста из группы "bsplinecurve" сетки "geometry":

bsplinecurve result 3 3 0 4 3 1 4 4  0 0 0 1  1 2 0 1   2 3 0 1  3 2 0 1 2   0 0 1 
set length 6.3759776321287411

Команда "bsplinecurve" создает B-кривую с именем "result" и данными параметрами контрольных точек и узлов. Второй строкой определяется переменная "length", значением которой указывается ожидаемая длина кривой. Это значение будет сравниваться с реальной длиной после того как тест завершится (логика сравнения реализована в терминаторе "end", лежащем уровнем выше). В этом и состоит аналитичность теста, так как оценивается не визуальная форма кривой, а ее численная характеристка. Тест простой, но эффективный, так как любое изменение формы кривой скорее всего повлечет изменение ее длины (ясно, что никакой строгости в этом утверждении нет). Синтетическая же сторона дела состоит в том, что результат моделирования «фотографируется» и помещается в отчет. Таким образом мы имеем понятный визуальный отклик для наших тестов.

Итак, что мы можем «перетащить» к себе в стороннее приложение? Другими словами, какие выгоды нам дает тестовая экосистема Draw?

  1. Полная тестовая инфраструктура. Ясно, как писать сценарии (это простые скрипты на Tcl) и как запускать их (это делает сам Draw). В заметке о тестировании с общих позиций мы вводили понятие диспетчера запуска тестов. Так вот, Draw является здесь вполне оригинальным диспетчером запуска.
  2. Многостраничные и красочные HTML-отчеты.
  3. Возможность запуска тестов на нескольких ядрах.
  4. Возможность «фотографировать» промежуточные и итоговые геометрические данные.

На практике, если вы уже используете библиотеку Open CASCADE Technology, то ничего не стоит «приручить» ее тестовую систему. Это натуральный выбор. Давайте теперь разберемся в деталях.

Запитываем Draw от собственного источника

Чтобы зарядить в обойму Draw свои собственные тесты, нам потребуется настроить две переменные окружения:

  • CSF_TestScriptsPath — директория, содержащая тестовые сетки (test grids).
  • CSF_TestDataPath — корневая директория вашего хранилища данных.

Когда Draw запускается, он пытается разыскать в текущей директории файл с именем DrawAppliInit (без расширения). Это обычный Tcl-скрипт, который будет выполнен только один раз, при старте Draw. На практике бывает полезно поместить в DrawAppliInit все те команды, с которых начинается работа пользователя в сессии Draw. Так мы оптимизируем его работу. Кроме того, в вашем распоряжении вся мощь языка Tcl (а также графической надстройки Tk), поэтому в DrawAppliInit можно определить и некоторые полезные процедуры. Ниже мы приводим пример содержимого DrawAppliInit:

#### DrawAppliInit ###
  puts "Custom Draw facilities"
  set env(CSF_TestScriptsPath) [file join [file dirname [file normalize [info script]]] draw]
  
  # Load necessary modules
  pload MODELING
  pload -MyCustomTurbineDraw
  
  # Makes snapshot of entire scene currently rendered in AXO viewer
  # and saves it to png file in results folder
  proc SnapShotAxo {case} {
    global env
    global imagedir
    global test_image
  
    set scriptname [regsub {.tcl} [regsub {.*/} [info script] {}] {}]
    puts "Saving image to ${test_image}_${case}.png"
    uplevel xwd $imagedir/${test_image}_${case}.png
  }

Мы видим, что переменная CSF_TestScriptsPath выставляется в скрипте DrawAppliInit (то же самое можно проделать и с переменной CSF_TestDataPath). Далее, при помощи инструкции "pload MODELING" в стартующую сессию Draw подгружаются все команды геометрического моделирования, которые теперь становятся доступны как при интерактивном запуске Draw, так и в тестовых сценариях. Принятая практика состоит написании собственных расширений для Draw, чтобы строить на них свои тесты. Скажем, если вы занимаетесь моделированием турбин, то целесообразно скомпоновать соответствующую моделирующую функциональность в человеко-понятные команды Draw, например:

turbines_read_blade # Читаем геометрию лопатки из файла
turbines_write_blade # Пишем геометрию лопатки в файл
turbines_build_blade # Строим лопатку
turbines_check_blade # Проверяем лопатку на корректность построения
...

Принципиально команды серии turbines_* ничем не отличаются от прочих команд OCCT — это обыкновенные функции C++, зарегистрированные в интерпретаторе Draw с тем или иными именем. Чтобы подгрузить их в Draw, пишется так называемый плагин, оформленный в виде динамической библиотеки. Листинг сценария DrawAppliInit, приведенный выше, предполагает, что соответствующие команды Draw определены в библиотеке с именем MyCustomTurbineDraw.dll (или .so для Linux). Конечно, путь до этой библиотеки должен быть настроен в переменной PATH.

Официальная документация поможет вам написать свой плагин для Draw. Делается это довольно просто.

Отметим процедуру SnapShotAxo, определенную в том же сценарии DrawAppliInit. Это не более чем «обвязка» над командой "xwd", которая делает снимок с вьювера. Именно из-за этой команды мы были вынуждены включить в сборку OCCT библиотеку Freeimage.

Теперь приведем типичный тестовый сценарий:

### Some test scenario, e.g. with filename A1 ###
  turbines_read_blade datafile.dat
  
  # Make 1-st snapshot after the input data is loaded
  axo; fit
  SnapShotAxo 1
  
  turbines_build_blade myBlade
  
  # Make 2-nd snapshot after the blade is constructed
  donly myBlade; fit
  SnapShotAxo 1

Каких-то особых правил написания тестов на Tcl нет. Вы можете задействовать любые возможности этого языка, дополняя их своими специфическими командами Draw и пользовательскими процедурами Tcl. Если в текстовом выводе сценария встретилось нехорошее слово, такое как "error" или "exception", то по умолчанию считается, что тест провалился. Выше мы видели, что такое поведение настраивается при помощи файла "parse.rules".