Почему юнит-тесты не работают в научных приложениях

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

    Статья будет состоять из нескольких разделов:
    1. «Введение», в котором я расскажу, почему решил написать ее
    2. «Иллюстрационный алгоритм». Здесь будет кратко описана научная проблема и алгоритм, который будет иллюстрировать дальнейшее изложение
    3. «Недостатки юнит-тестов» Здесь будут приведены аргументы, объясняющие недостатки юнит-тестов для научных приложений
    4. Заключение

    Сразу замечу, что я не рассматриваю пользу от TDD как от способа разработки архитектуры, а исключительно с точки зрения тестирования программы.

    Введение


    Во время недели воздухоплавания на хабре на главной оказалась статья Цифровые самолеты [1], и занятный комментарий к ней [2] о том, что 20 лет назад не было юнит-тестов и вообще нормальных методов разработки, поэтому распространенной была схема «triple-triple redundant architecture», в частности, написание одной и той же программы тремя разными командами на разных языках с целью исключения программных ошибок.

    Тем не менее, по свидетельствам очевидцев ([3], кстати, это еще одна прекрасная книга прекрасного ученого Ричарда Фейнмана, обретшего вторую жизнь на хабре) если и не Test Driven Development (TDD далее), то ручное тестирование было очень важным этапом разработки.
    Именно поэтому у меня появилось желание проанализировать подход к разработке научных приложений и поделиться мыслями по этому поводу, в частности, рассказать, что TDD—это вовсе не silver bullet для поиска ошибок, как можно подумать сперва.

    Иллюстрационный алгоритм


    В последние 20 лет развивался необычный метод для моделирования гидродинамики: Lattice-Boltzmann Method (далее — LBM) [4], [5]. Я принимал участие в реализации этого алгоритма на C++ для института Макса Планка в Германии.

    Во избежание неизбежных вопросов: да, уже существуют коммерческие и открытые пакеты, использующие этот метод [6]; но изначально планировалось использование модификации метода, которая должна была давать выигрыш в производительности и потребляемой памяти; тем не менее, от нее мы последовательно отказались (сначала для массопереноса, потом для теплопереноса). К тому времени было написано много кода, и мы решили продолжить собственную реализацию.

    Гидродинамика, как известно, описывается уравнением Навье-Стокса. При его моделировании можно использовать, например, Finite Element Method [7]. К сожалению, у него есть недостатки — относительная сложность распараллеливания, невозможность моделирования многофазных потоков, трудности моделирования потоков в пористых средах. Этих недостатков лишен LBM.

    Для разреженных газов справедливо уравнение Больцмана [8], которое описывает, как меняется плотность распределения частиц по скоростям в каждой точке пространства со временем. Макроскопически это уравнение эквивалентно уравнению Навье-Стокса (то есть после перехода к макроскопическим величинам — плотности и скорости).

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

    Следующий этап — это дискретизация уравнения Больцмана: по времени, по пространственным координатам (получаем пространственные узлы для моделирования) и по возможным направлениям частиц в каждом пространственном узле. Направления выбираются специальным образом, и всегда указывают в некоторые соседние узлы.

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

    Недостатки юнит-тестов


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

    Отсутствие простых тестов

    Для сложных алгоритмов и методов тяжело придумать простые тесты.

    Это, в общем-то, очевидно. Часто сложно подобрать простые входные параметры формулы так, чтобы на выходе получить простое и понятно число. То есть вы, конечно, отдельно на листочке или в Матлабе можете посчитать, какие параметры надо подать на вход, чтобы на выходе тестируемого метода получить единицу; но и входные параметры, и единица на выходе будут скорее магическими числами. Пример — это распределение Максвелла [9]. В алгоритме LBM оно используется в несколько измененном виде.

    Малая область покрытия

    Даже если вам удастся придумать простой тест для сложной формулы или алгоритма, то этот тест, скорее всего, не найдет большей части ошибок.

    Действительно, иногда простой и легко читаемый тест можно придумать: например, если вы тестируете, как написали матрицы поворота в вашем 3D движке (думаю, немало жителей хабра баловались этим), то поворот на нулевые углы по всем осям должен возвращать единичную матрицу. Тем не менее, часто эти тесты не обнаруживают большинства ошибок. Например, если вы вместо синуса угла поворота (в элементах матрицы поворота) станете использовать тангенс, то такой простой тест пройдет.

    Еще пример: вам необходимо реализовать граничные условия для алгоритма LBM. Эти условия должны дополнять состояние узла, когда последний не полностью окружен «жидкими» соседями (например, он находится у стенки). Можно придумать и простой тест — если все соседи находятся в равновесных состояниях (то есть скорости распределены по Максвелловскому закону), то после итерации граничный узел тоже будет в равновесном состоянии. Тем не менее, оказалось, что этот тест пропускает большую часть ошибок…

    Можно возразить — в бизнес-приложениях тесты тоже не призваны покрывать все возможные входные параметры. Тем не менее, отличие научных приложений в том, что они обычно оперируют с множествами другой мощности [10]. Бизнес-приложения в основном работают с конечными или счетными множествами (конечной мощности или мощности алеф-нуль: булевские переменные, строки, целые числа), а научные — с несчетными множествами (действительные или комплексные числа).

    Малая скорость возрастания ошибок

    Из-за того, что типичные множества несчетны, тяжело сразу обнаружить ошибку.

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

    Пример: в одном из важных циклов алгоритма LBM был перепутал continue с break. Тем не менее, все тесты проходили. Обнаружилась ошибка только после ручной проверки алгоритма на простой системе — потоке Пуазейля [11], когда после 100000 итераций, или 5 минут(!) моделирования системы 32х64 узла (начиная со стационарного состояния) распределение скоростей оказалось несколько асимметричным (максимальная относительная ошибка — порядка 1e-10).

    Отсутствие модульности

    Для многих методов невозможно создать модульные тесты.

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

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

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

    Большие фасады

    Если алгоритм нетривиальный, то за одним public методом класса, реализующего этот алгоритм, может скрываться 10-15 методов и 300-400 строк сложного кода [12].

    Даже так: 300-400 строк сурового, беспощадного, не поддающегося анализу кода. В этом классе могут быть 5 переменных типа a, b, gamma, вызовы методов библиотек LAPACK [13], BLAS, генерация случайных чисел и кто знает что еще. И вы можете всей душой желать назвать переменную «a» как-то нормально, потому что знаете, что при разработке корпоративного приложения вас бы долго ругали за такое имя, но не можете, потому что так она называется в оригинальной статье, и потому что она не несет никакого явного смысла. Лучшее, что вы можете сделать, это указать ссылку на статью в начале класса.

    И даже если вы придумаете простой модульный тест, с простыми входными параметрами для этого единственного public метода, и с простым выходным значением, и он в один прекрасный момент перестанет проходить, то вам не удастся найти ошибку в этом классе даже путем самозабвенного дебага или ультимативным методом внимательного чтения.

    После столкновения с такой ситуацией лучшим решением оказалось переписать этот алгоритм на MATLAB и сравнивать вычисления построчно.

    Заключение


    Надеюсь, мне удалось привести достаточно веские аргументы и убедить читателей хабра, что зачастую написание одной и той же программы на разных языках разными командами — это прекрасное и (в случае жизненно важных систем) неизбежное дополнение для модульного тестирования, TDD, Interface Driven Development, Agile и прочих современных методик.

    Спасибо за прочтение!

    Ссылки


    [1] Цифровые самолеты
    [2] Комментарий к статье Цифровые самолеты
    [3] Какое тебе дело до того, что о тебе думают другие, глава «Многострадальное приложение»
    [4] Lattice Boltzmann method
    [5] Книги по LBM. Некоторые доступны через google books
    [6] Parallel Lattice Boltzmann Solver
    [7] Finite element method
    [8] Boltzmann equation
    [9] Maxwell-Boltzmann distribution
    [10] Cardinality
    [11] Поток Пуазейля
    [12] Паттерн Фасад
    [13] LAPACK

    P.S. Я догадываюсь, что для большинства читателей сайта алгоритм несколько экзотичен, тем не менее, думаю, это лучшая иллюстрация. В конце концов, куда ж без научных алгоритмов в статье о них самых.

    P.P.S. Ссылки на научные статьи я не привожу, потому что все они доступны только по подписке.

    P.P.S. Спасибо пользователю SychevIgor за инвайт!

    UPD


    После прочтения комментариев думаю, что надо избавляться от фобии магических чисел и писать для всех модулей (типа BoltzmannDistributionProvider) табличные тесты для сравнения с расчетами из Matlaba или расчетами работающего состояния приложения. А также добавить аналогичные системные тесты (например, просчитать динамику системы 5х5 узлов единожды, записать в файл, и сравнивать с этим файлом результаты прогона каждый раз). Всем спасибо за советы!
    Поделиться публикацией
    Комментарии 60
      +11
      А кто говорил, что юнит тесты нужно пихать куда не попадя?
        0
        Да уж, модульные тесты явно не подходят для таких систем. В ограничения ещё можно добавить код, который должен выполняться в нескольких потоках.

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

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

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

              Для примера.

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

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

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

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

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

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

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

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

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

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

                                          А насколько, по вашему мнению, к данному коду применины приемы рефакторинга из книги Фаулера?
                                            0
                                            К том конкретному алгоритму, по-моему, применимо очень мало способов. Дело вот в чем. Алгоритм выглядит примерно так: посчитать матрицу K (формула на строчку), посчитать вектор a (формула на строчку), и тп — 10 пунктов. Максимум, что мне удалось сделать, это разбить класс на методы типа ComputeMatrixK, ComputeVectorA и тп. Выносить их в отдельные провайдеры глупо--это будут классы типа MatrixKProvider, VectorAComputer и тп. Они только хуже сделают, а тестировать их все равно невозможно--у них нет ясного смысла.
                                              0
                                              а можете показать код?
                                                0
                                                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--те самые бессмысленные вектора.
                                                  0
                                                  это не самый страшный код :)
                                                  но, все же даже тут применимы ExtractMethod, ExtractVariable. А если у вас не только в этом методе такая текучка параметров, то и ExtractClass может быть к месту.
                                            0
                                            Гм но ведь можно наверно подойти как-то с другой стороны — например, проверять, чтобы при моделировании сколько жидкости втекло, столько же и утекло, чтобы она симметрично текла, чтобы не уходила в стенку там.
                                              +1
                                              собственно, примерно так и делают. Однако, если жидкость в стенку таки утекает (а эта утечка может быть на уровне 5-го знака после запятой, происходить раз в год во время солнечного затмения при движении жидкости на северовосток), то найти причину невозможно. Нету в программе процедурки, которая отвечает именно за утечку через твердую границу, там все очень нетривиально.
                                              +1
                                              А у меня вот чисто практический вопрос: если дойти до конца вашего вычислительного центра, то наверняка там обнаружатся несколько градирен всё это охлаждающих. Так с какой погрешностью эти все Навье-Стоксы-Больцманы-Максвеллы-Фейнманы-ПардонЕслиКогоЗабыл опишут распределение температур в сиих юнитах, при том, что там куда как больше 32х64 датчиков легко в 3D разместить можно и показаний 5 минут ждать не придется, невзирая ни на какие граничные условия?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                                                        Самое читаемое