Pull to refresh

Comments 60

А кто говорил, что юнит тесты нужно пихать куда не попадя?
Да уж, модульные тесты явно не подходят для таких систем. В ограничения ещё можно добавить код, который должен выполняться в нескольких потоках.

Спасибо за освещение интересной области в программировании. Все-таки скажите, как Вы тестируете написанную систему?
Многопоточный код — не помеха unit-тестированию. Он может усложнять поиск ошибок, но не является препятствием для тестирования.

Проблемой при тестах являются «внешние факторы» — привязка к системному времени, использование random и т.д.
У меня ключевая функциональность с многопоточностью покрыта юнит-тестами. И я не вижу принципиальных проблем (кроме самой сложности написания) в использовании таких тестов. Ну, а сложность написания у каждого своя.

Если известна конкретная ситуация в которой возникает проблема, то удобно написать тест, в котором потоки будут приводиться к нужному состоянию, а потом запускаться. В ручном же режиме, проблема может возникать раз в неделю и её будет практически невозможно дебагить.
Шикарно, можете об этом статью написать? Подводные камни и решения? Будет очень интересно.
Кстати, скажу по секрету, все системы для моделирования гидродинамики по умолчанию многопоточные, потому что иначе от них никакого толку:) Даже запись результатов моделирования в файлы делают многопоточной обычно--из-за объемов данных. Да и программы для просмотра этих результатов--тоже, как ни странно, многопоточные. www.paraview.org/ (Parallel Viewer)
В данном случае автор перепутал понятие функциональных тестов и модульных.
В основном модульные тесты, нужны для того что бы проверить целостность системы, и не сломались ли где интерфейсы системы при разработке нового функционала.
В данном случае, в статье больше подходит понятие функционального тестирования — выполняет ли программа свою глобальную задачу или нет.

Для примера.

если
a + b = c — то проверить результат c — это будет функциональный тест.
если
testVal = 5;
(setA(testVal) && getA() == testVal) — тут же мы проверим только функцию setA правилньо ли она сохранила значение.

И когда мы будет провеять для первого примера setA и setB — это будет модульное тестирование. А вот конечный результат программы — уже функциональное.

Хотя как Вы сами видете грань очень тонкая.
Честно сказать, с этим проблемы. Поэтому и статью написал)
Там, где можно, старался писать тесты. Но в основном это части программы, похожие на обычные бизнес-приложения (например, разбиение глобальной геометрии на локальные части--для каждого потока своя).
В случаях похуже приходилось писать что-то среднее между модульными и интеграционными: то есть для тестирования «провайдера граничных условий» я строил совсем маленькую систему 2Х3 узла в равновесном состоянии, и к верхнему среднему узлу применял этот провайдер (чтобы он восстановил неизвестные параметры узла). После применения такого провайдера узел должен остаться в равновесном состоянии. С одной стороны, тест модульный, так как тестирует небольшой замкнутый кусочек системы (одну функцию одного класса), а с другой--нужно долго готовить его. Но по существу это smoke-test, потому что почти никаких ошибок не ловит. Так что часто просто ручное тестирование использовалось.
После прочтения комментариев я думаю, что надо избавиться от фобии магических чисел и написать для всех модулей (типа EquilibriumDistributionProvider) табличные тесты для сравнения с расчетами из Matlaba или расчетами работающего состояния приложения. И системные тесты такие же добавить (просчитать динамику системы 5х5 узлов единожды, записать в файл, и сравнивать с этим фалом результаты прогона каждый раз). Примерно так.
А кто говорил где их применять, а где нет?!
Автор поделился своим опытом на эту тему, благодаря чему этот вопрос стал более исследованным.
На мой взгляд, это примерно то же самое, что говорить: «зачем мне тестировать слой бизнес-логики в моей e-Commerce системе?! Там же есть формулы, расчеты, сложные логические условия, запросы к базе [если вы пользуетесь паттерном Repository и ORM, то высокоуровневые запросы, скорее всего, будут в слое бизнес-логики], зависимости между классами и тп». Разница с научными приложениями в том, что в случае e-Commerce вы-таки можете добавить эффективные юнит-тесты там, где они нужны, а в научных--не всегда. Что я и надеялся продемонстрировать.
Согласен, что юнит-тесты не всегда оправдывают ожидания; но большинство из перечисленных минусов часто можно устранить более мелким и детальным дроблением на юниты. Появятся и быстрота, и покрытие, и скорость. Что же касается модульности и фасадов, это вопрос дизайна приложения. Если вы проектируете модули так, что каждый взаимодействует с каждым, то очень скоро вы не сможете поддерживать не только набор юнит-тестов и на само приложение. То же самое касается и фасадов. Всё же надо стремиться к компактным интерфейсам и чёткой организации со строгими ролями, а не «все используют всех во все дыры».
АХАХАХА! Бросайте вы CMS на похапе писать — до добра не доведёт.
Не совсем unit, но обычно в случае сложных алгоритмов делают кристально-очевидный прототип, и его результаты используют как эталон. Как вариант можно просчитать некоторые системы на MatLab, Mathemаtica итд и их результаты использовать как тесты. Опять же про модульность — сложный алгоритм все таки делают итерационно и каждый слой можно проверять отдельно.
отчасти по этим причинам я использую фортран для моделирования газодинамики. Простой язык, ясный синтаксис, вагон отлаженных ранее сабрутин, зачем еще какие-то юниттесты? :)
Неужели Фортран настолько крут, что там есть функции типа CalculateHydrodynamicDispersionInPorousTube :)? [Пошел учить Фортран...]
конечно есть! Она записана где-то на третьей бобине сверху, из стопки лежащей в левом отделении шкафа вашего научного руководителя. Была еще другая версия, она пробита на перфокартах в пачке под номером 372/4, у него же, в нижнем ящике стола, но примерно половину ее съели крысы, а из оставшихся треть была использована в качестве салфеток под закусь во время празднования дня рождения замдекана :)
Опыт показывает, что глупые опечатки или неправильно расставленные скобочки гораздо чаще встречаются в кристально ясном коде на Maple, чем в программе на каком-нибудь там Паскале.
Ещё не известно, какой язык из двух более кристально ясный. Особенно по количеству скобочек и пр.
Offtop: Не подскажете хорошую книгу по Maple (желательно 10-11)? А то мой опыт закончился 4 версией и было это 10 лет назад… А сейчас нужно поработать с символьным пакетом.
maplesoft.com/support/help/index.aspx

Полнее и точнее вряд ли можно найти. Существенных отличий немного, принципы всё те же. Основные отличия новых версий от старых в наличии новых пакетов и усовершенствовании старых, а в остальном как езда на велосипеде — один раз научился и на всю жизнь.
Хотя есть всякие мелочи вроде того, что в одних версиях порядок переменных при выводе решения каждый раз выбирается произвольно, а в других каждый раз одинаково. Но такие отличия нигде в одном месте не описаны, да и вряд ли это нужно.
Если использовать Symbolic Toolbox в MATLAB в качестве системы компьютерной алгебры, то обычно хватает встроенного мануала. Раньше это и была обертка над Maple, правда, сейчас они переключились на другую реализацию--MuPad.
А почему набор подготовленных аналитических решений (коих достаточно в области газодинамики и разреженной ГД так же) не может считаться набором тестов?
Самое главное--они не модульные. Обычно вам надо запускать всю программу целиком. Во-вторых, они медленные (см. раздел Малая скорость возрастания ошибок). То есть, чтобы сравнения с аналитическими решениями были хоть сколько репрезентативны, надо довольно долго проводить моделирование. Наконец, эти решения сами по себе далеко не тривиальны, то есть в качестве простого автоматического теста не подходят. Такие решения--это обычно майлстоуны в процессе разработки, и проверяются вручную. Примеры: www.openfoam.com/docs/user/cavity.php, en.wikipedia.org/wiki/Taylor%E2%80%93Couette_flow, physics.ucsd.edu/was-daedalus/convection/rb.html
А еще бывает, что ошибка слабо влияет на конечный результат, и ее сложно определить. Уменя тож в газодинамике была ошибка типа неправильного условия выхода из цикла… а результат моделирования до и после исправления отличался крайне незначительно, хотя ошибка была достаточно большой.
В некоторых задачах бывает так, что результат каких-то сложных вычислений в точно решаемых случаях на конечный результат почти не влияет. Но на то они и точно решаемые, что там можно и без этих вычислений обойтись. А в сложных случаях, в которых ответ неизвестен, они [вычисления] влияют сильно.
Я это и сам чувствовал — но написать не смог бы. Вы отлично всё написали.

Попробую перевести терминологию на русский.

triple-triple redundant architecture — архитектура с трижды-трёхкратным дублированием.

lattice-Boltzmann Method — в интернете я видел перевод «метод решёток Больцмана», хотя я бы назвал его «клеточный метод Больцмана».

Finite Element Method — метод конечных элементов.
Ещё говорят «Метод решеточных уравнений Больцмана»
> Finite Element Method [7]. К сожалению, у него есть недостатки — относительная сложность распараллеливания

Странно. Метод конечных элементов сводится к решению СЛАУ, которое и составляет большую часть вычислений. А для решения СЛАУ есть множество библиотек, параллельно их решающих. В чем тогда проблема распараллеливания здесь?
По-моему, смысл вот в чем. Предположим, что у вас есть блочная матрица с блоками [1 2 3; 4 5 6; 7 8 9]. На каждый процессор по блоку. При параллельном решении СЛАУ с этой системой каждому процессору нужно будет взаимодействовать с каждым (поправьте меня, если не прав). В методе LBM данному процессору нужно будет взаимодействовать только с соседями (то есть процессор 1--с процессорами 2, 5, 4). Как-то так.
«Если алгоритм нетривиальный, то за одним public методом класса, реализующего этот алгоритм, может скрываться 10-15 методов и 300-400 строк сложного кода [12].

Даже так: 300-400 строк сурового, беспощадного, не поддающегося анализу кода. „

А насколько, по вашему мнению, к данному коду применины приемы рефакторинга из книги Фаулера?
К том конкретному алгоритму, по-моему, применимо очень мало способов. Дело вот в чем. Алгоритм выглядит примерно так: посчитать матрицу K (формула на строчку), посчитать вектор a (формула на строчку), и тп — 10 пунктов. Максимум, что мне удалось сделать, это разбить класс на методы типа ComputeMatrixK, ComputeVectorA и тп. Выносить их в отдельные провайдеры глупо--это будут классы типа MatrixKProvider, VectorAComputer и тп. Они только хуже сделают, а тестировать их все равно невозможно--у них нет ясного смысла.
void MaxEntropyMassDynamics::FillRightVector(doublereal* rightVector, Float knownDensity,
Float* knownMomentum, Float* rightSideMultipliers)
{
VectorUtilities::InitializeWith(rightVector, 0, unknownPopulationsSize);

int i;
for(i = 0; i < unknownPopulationsSize; i++)
{
rightVector[i] = knownDensity * rightSideMultipliers[i];
}

for(i = 0; i < dimensions; i++)
{
rightVector[unknownPopulationsSize + i] = knownDensity * currentNode->Velocity[i] — knownMomentum[i];
}
}

Float--это внутренний typedef, doublereal--typedef CLAPACK; используются ссылки на массивы, а не std::vector вроде как для скорости и для интеграции с lapack. rightVector и rightSideMultipliers--те самые бессмысленные вектора.
это не самый страшный код :)
но, все же даже тут применимы ExtractMethod, ExtractVariable. А если у вас не только в этом методе такая текучка параметров, то и ExtractClass может быть к месту.
Гм но ведь можно наверно подойти как-то с другой стороны — например, проверять, чтобы при моделировании сколько жидкости втекло, столько же и утекло, чтобы она симметрично текла, чтобы не уходила в стенку там.
собственно, примерно так и делают. Однако, если жидкость в стенку таки утекает (а эта утечка может быть на уровне 5-го знака после запятой, происходить раз в год во время солнечного затмения при движении жидкости на северовосток), то найти причину невозможно. Нету в программе процедурки, которая отвечает именно за утечку через твердую границу, там все очень нетривиально.
А у меня вот чисто практический вопрос: если дойти до конца вашего вычислительного центра, то наверняка там обнаружатся несколько градирен всё это охлаждающих. Так с какой погрешностью эти все Навье-Стоксы-Больцманы-Максвеллы-Фейнманы-ПардонЕслиКогоЗабыл опишут распределение температур в сиих юнитах, при том, что там куда как больше 32х64 датчиков легко в 3D разместить можно и показаний 5 минут ждать не придется, невзирая ни на какие граничные условия?

PS Вопрос практический, про погрешность в 1е-10 и биквантернионы рассказывать не обязательно, просто интересно сравнить
Дело же не только в то том что бы получить поле температур рядом стоящего «утюга». С помощью метода решеточных уравнений Больцмана моделируют рост дендритов например. Вам в такой задача на практике ни какие датчики не помогут.
Ответ немного непрямой: программа в моем случае была рассчитана на моделирование потока в пористой трубке популярных химических реакторов (http://en.wikipedia.org/wiki/Fluidized_bed_reactor). В этом институте пробовали провести измерения температур физически, но оказалось, что датчики, во-первых, плотно не расставить, во-вторых, можно расставить только снаружи реактора, в-третьих, они искажают поле скоростей и температур своим присутствием.
>> Почему юнит-тесты не работают в научных приложениях

Излишне категоричное название. Лучше было бы назвать: «Почему я не смог применить юнит-тесты в своей работе»
Как же приятно прочитать в статье на Хабре слова «метод конечных элементов» и «уравнения Навье-Стокса» :)

По-моему, чтобы заметить, что test driven development не всегда работает, достаточно даже не очень большого учебного проекта, в котором моделируется какой-нибудь нетривиальный процесс… Наверняка у многих во время обучения в университете были подобные задачи, надо просто вспомнить.
Имхо в статье неправильно противопоставляются unit vs ручное тестирование. Правильнее рассуждать о автоматизированное vs ручное тестированием.

Unit тесты, лишь небольшой частный случай автоматизированного тестирования.

Ваши алгоритмы действительно лучше тестировать просто путем построчного сравнения логов результатов вычислений в матлабе и внешней реализации.
Под конец написали фигню. Если возможно сделать ручное построчное сравнение, то почему бы его не автоматизировать?
Есть некоторые вещи, которые принципиально не поддаются unit-тестированию. Например, таймер времени или ещё какое-нибудь значение, зависящее от внешних факторов. Но, в большинстве случаев, внешний фактор удаётся изолировать, а остальное оттестировать.

Если результаты алгоритма повторяемы, а код алгоритма время от времени подвергается модификации — надо писать тест. Это снижает время повторной проверки после модификации и даёт представлении о том, что вы думали, когда писали исходный код и каких результатов ожидали.
Безусловно покрыть сложную математику хорошими тестами сложно. Сами тесты будут полны магических чисел и допущений.

Но это совершенно не значит, что от них нужно отказываться. Вполне возможно писать smoke tests, прохождение которых не будет значить, что код работает правильно, но которые хотя бы покажут, что сломалось то, что работало ранее.

У меня был опыт разработки программ с математикой с тестами и без. С тестами значительно удобнее.

Разобрал ветвь алгоритма на бумаге, реализовал, забил в тест вход-выход с бумаги — и уже уверен, что хотя бы в одном случае код работает и не содержит дурацких ошибок.

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

Да, во многих случаях smoke-тесты не заметят ошибки. Но ради тех ошибок, которые они выявят их стоит писать.

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

Создавая алгоритм моделировании гидродинамики, почему Вы, для его верификации, не соотносите полученные данные с опытными? Ведь их совпадение лучшее подтверждение правильности выбранного решения.
У опытных данных есть проблемы: 1. они с погрешностями (огромными для верефикации численных методов) 2. никому не удастся померять поле скоростей в сосуде с жидкостью. Максимум--какие-то усредненные значения, либо безразмерные величины (типа числа Нуссельта) 3. измерения искажают поле скоростей и температур.
Поэтому используют либо сравнение с известными аналитическими решениями, либо с другими численными. Притом бенчмарки выбирают так, чтоб проверялись какие-то характерные эффекты. www.cfd-online.com/Wiki/Lid-driven_cavity_problem
А вы не путаете понятие финкциального тестирования с модульным тестированием?
Признаюсь, я не большой эксперт в области тестирования ПО, но, кажется, это описания тестов в разных иерархиях: unit->integration->system testing и functional/non-functional. en.wikipedia.org/wiki/Software_testing
Unit testing
Main article: Unit testing

Unit testing refers to tests that verify the functionality of a specific section of code, usually at the function level. In an object-oriented environment, this is usually at the class level, and the minimal unit tests include the constructors and destructors.[25]

These type of tests are usually written by developers as they work on code (white-box style), to ensure that the specific function is working as expected. One function might have multiple tests, to catch corner cases or other branches in the code. Unit testing alone cannot verify the functionality of a piece of software, but rather is used to assure that the building blocks the software uses work independently of each other.

Unit testing is also called component testing.

Вы же пытаесть протестировать — конечную функцию приложения. Я тоже не совсем прав. Модульное тестирование это часть функционального тестирования.
Интересно, в Вашем случае можно было бы использовать формальную верификацию?
Unit Testing 101 — начальный курс модульного тестирования. Смотреть в Firefox-е. Читать всем!

А ваши алгоритмы прекрасно тестируются с помощью табличных приёмочных тестов — глядеть Fitnesse Acceptance Testing.
Очень жаль, что «101» нельзя залить на кпк и спокойно почитать в метро.
Для таких задач довольно неплохо работают интеграционные тесты — мы знаем результаты предыдущих версий системы на определенных данных, мы предполагаем что они правильные и не должны измениться — мы прогоняем continuous integration tests на каждый коммит (или по шедулеру) и если они начинают обваливаться то идем и разбираемся, чего мы там накоммитили.
Наверное, можно еще сделать хорошую визуализацию и самому следить за процессом.
«TDD—это вовсе не silver bullet для поиска ошибок» — это 5! :)
как-нибудь полистайте такую книженцию — Фредерик П.Брукс. Мифический человеко-месяц или как создаются программные системы.
там человек лет 20 назад (или уже больше? 8-0 ) в главе 17 приходит к выводу — «Серебряной пули нет» :)))) короче это книга — классика — советую хотя бы полистать… теперь про TDD — это же набор практик — никто не заставляет применять их все (ага мы же помним — серебряной пули нет), но юнит тесты при наличии сложных расчетов необходимы! более того они прекрасно ложатся на логику без гуи… естественно, есть проблемы — где брать контрольные точки по частям алгоритма, необходимость обновлять все это барахло при изменении алгоритма,… сам был в такой ситуации — писал числодробилку, формулы были мабуть попроще, но их несколько раз меняли — уточняли модель…
после каждого такого изменения тихо матерясь лезем, обновляем тесты, меняем логику, но после этого за любой рефакторинг я брался спокойно — тесты не дадут сломать логику (естественно, при соответствующем покрытии)… за примерно 500 часов разработки и 3-4 изменения формул тесты мне стрельнули всего пару раз… но, время, сэкономленное на тестировании после каждой итерации, не сравнить с временем поддержки актуальности тестов — выгода во много раз!
более того, данные для юнит тестов можно готовить автоматически/полуавтоматически. когда мне за месяц 2-ды поменяли формулы и я столько же раз обновил тесты — я тут же задумался над этим ;) вам, похоже, тоже стоит ;)
Only those users with full accounts are able to leave comments. Log in, please.