Я твердо убежден в ценности использования тестов, которые проверяют полную программу (например, тесты сходимости), включая автоматизированный набор регрессионных тестов . После прочтения некоторых книг по программированию у меня появилось мучительное чувство, что я "должен" написать модульные тесты (т. Е. Тесты, которые проверяют правильность одной функции и не сводятся к выполнению всего кода для решения проблемы). , Тем не менее, модульные тесты не всегда соответствуют научным нормам и в конечном итоге кажутся искусственными или бесполезными.
Должны ли мы написать модульные тесты для исследовательских кодов?
programming-paradigms
testing
Дэвид Кетчесон
источник
источник
Ответы:
В течение многих лет я был в заблуждении, что у меня не было достаточно времени для написания модульных тестов для моего кода. Когда я писал тесты, они были раздутыми, тяжелыми вещами, которые только побуждали меня думать, что я должен когда-либо писать модульные тесты только тогда, когда я знал, что они нужны.
Затем я начал использовать Test Driven Development и обнаружил, что это полное откровение. Теперь я твердо убежден, что у меня нет времени не писать юнит-тесты .
По моему опыту, разрабатывая с учетом тестирования, вы получаете более чистые интерфейсы, более сфокусированные классы и модули и, как правило, более твердый , тестируемый код.
Каждый раз, когда я работаю с устаревшим кодом, который не имеет модульных тестов и должен что-то тестировать вручную, я продолжаю думать, что «это было бы намного быстрее, если бы этот код уже имел модульные тесты». Каждый раз, когда мне приходится пытаться добавить функциональность модульного теста в код с высокой связью, я продолжаю думать, что «это было бы намного проще, если бы оно было написано в разобщенном виде».
Сравнивая и сравнивая две экспериментальные станции, которые я поддерживаю. Один существует уже некоторое время и имеет большой унаследованный код, а другой относительно новый.
При добавлении функциональности в старую лабораторию часто случается, что вы зашли в лабораторию и потратили много часов на то, чтобы разобраться в последствиях необходимой им функциональности и о том, как я могу добавить эту функциональность, не затрагивая другие функциональные возможности. Код просто не настроен для автономного тестирования, поэтому практически все должно быть разработано в режиме онлайн. Если бы я действительно пытался разрабатывать в автономном режиме, я бы получил больше ложных объектов, чем было бы разумно.
В более новой лаборатории я обычно могу добавить функциональность, разработав ее в автономном режиме за своим рабочим столом, высмеивая только те вещи, которые требуются немедленно, и затем проводя в лаборатории недолгое время, решая оставшиеся проблемы, которые не были устранены. -линия.
Для ясности, а так как @ naught101 спросил ...
Я склонен работать над экспериментальным программным обеспечением для контроля и сбора данных с некоторым специальным анализом данных, поэтому сочетание TDD с контролем версий помогает документировать как изменения в базовом оборудовании эксперимента, так и изменения требований к сбору данных с течением времени.
Однако даже в ситуации разработки исследовательского кода я мог видеть существенную выгоду от кодификации предположений, а также способность видеть, как эти предположения развиваются с течением времени.
источник
Научные коды, как правило, имеют созвездия взаимосвязанных функций чаще, чем бизнес-коды, над которыми я работал, обычно из-за математической структуры проблемы. Поэтому я не считаю модульные тесты для отдельных функций очень эффективными. Тем не менее, я думаю, что есть класс модульных тестов, которые являются эффективными и все еще сильно отличаются от целых программных тестов тем, что они нацелены на определенную функциональность.
Я просто кратко определю, что я имею в виду под этими видами тестов. Регрессионное тестирование ищет изменения в существующем поведении (проверенные каким-либо образом) при внесении изменений в код. Модульное тестирование запускает фрагмент кода и проверяет, что он дает желаемый результат на основе спецификации. Они ничем не отличаются, так как исходный регрессионный тест был модульным тестом, так как я должен был определить, что вывод был действительным.
Еще два примера модульного тестирования, происходящего из PyLith , - это определение точки, которая является единственной функцией, для которой легко получить синтетические результаты, и создание связных ячеек с нулевым объемом в сетке, которая включает в себя несколько функций, но обращается к ограниченному фрагменту функциональность в коде.
Существует много тестов такого рода, в том числе тесты консервации и консистенции. Операция ничем не отличается от регрессии (вы запускаете тест и проверяете выходные данные на соответствие стандарту), но стандартный вывод получается из спецификации, а не из предыдущего запуска.
источник
С тех пор, как я прочитал о разработке через тестирование в Code Complete, 2-е издание , я использую инфраструктуру модульного тестированиякак часть моей стратегии развития, и это значительно повысило мою производительность за счет сокращения количества времени, которое я тратил на отладку, потому что различные тесты, которые я пишу, являются диагностическими. В качестве дополнительного преимущества я гораздо увереннее в своих научных результатах и неоднократно использовал свои модульные тесты для защиты своих результатов. Если в модульном тесте возникает ошибка, я могу довольно быстро выяснить, почему. Если мое приложение дает сбой и все мои модульные тесты пройдены, я делаю анализ покрытия кода, чтобы увидеть, какие части моего кода не выполняются, а также перебираю код с помощью отладчика, чтобы точно определить источник ошибки. Затем я пишу новый тест, чтобы убедиться, что ошибка остается исправленной.
Многие из тестов, которые я пишу, не являются чисто юнит-тестами. Строго определенные юнит-тесты должны выполнять функции одной функции. Когда я могу легко протестировать одну функцию, используя фиктивные данные, я делаю это. В других случаях я не могу легко смоделировать данные, необходимые для написания теста, который выполняет функциональность данной функции, поэтому я протестирую эту функцию вместе с другими в интеграционном тесте. Интеграционные тестыпроверить поведение нескольких функций одновременно. Как указывает Мэтт, научные коды часто представляют собой совокупность взаимосвязанных функций, но часто определенные функции вызываются последовательно, и могут быть написаны модульные тесты для проверки выходных данных на промежуточных этапах. Например, если мой производственный код вызывает пять последовательных функций, я напишу пять тестов. Первый тест вызовет только первую функцию (так что это модульный тест). Затем второй тест вызовет первую и вторую функции, третий тест вызовет первые три функции и так далее. Даже если бы я мог написать модульные тесты для каждой отдельной функции в моем коде, я бы в любом случае написал интеграционные тесты, потому что ошибки могут возникать при объединении различных модульных частей программы. Наконец, после написания всех модульных и интеграционных тестов, я думаю, мне нужно Я заверну свои примеры в модульных тестах и использую их для регрессионного тестирования, потому что я хочу, чтобы мои результаты были повторяемыми. Если они не повторяются, и я получаю разные результаты, я хочу знать, почему. Провал регрессионного теста не может быть реальной проблемой, но это заставит меня выяснить, заслуживают ли новые результаты такой же достоверности, как старые.
Наряду с модульным тестированием также стоит статический анализ кода, отладчики памяти и компиляция с флагами предупреждений компилятора для обнаружения простых ошибок и неиспользуемого кода.
источник
По моему опыту, поскольку сложность кодов научных исследований возрастает, в программировании требуется очень модульный подход. Это может быть болезненным для кодов с большой и древней базой (
f77
кто-нибудь?), Но это необходимо для продвижения вперед. Поскольку модуль строится вокруг определенного аспекта кода (для приложений CFD, например, «Граничные условия» или «Термодинамика»), модульное тестирование очень важно для проверки новой реализации и выявления проблем и дальнейших разработок программного обеспечения.Эти модульные тесты должны быть на один уровень ниже проверки кода (могу ли я восстановить аналитическое решение моего волнового уравнения?) И на 2 уровня ниже проверки кода (могу ли я предсказать правильные пиковые среднеквадратичные значения в моем турбулентном потоке в трубе), просто гарантируя, что программирование (правильно ли переданы аргументы, указатели указывают на правильную вещь?) и "математика" (эта подпрограмма вычисляет коэффициент трения. Если я ввожу набор чисел и вычислю решение вручную, дает ли подпрограмма то же самое результат?) верны. По сути, на один уровень выше того, что могут заметить компиляторы, т.е. основные синтаксические ошибки.
Я определенно рекомендовал бы это по крайней мере для некоторых важных модулей в вашем приложении. Однако нужно понимать, что это чрезвычайно утомительно и отнимает много времени, поэтому, если у вас нет неограниченной человеческой силы, я бы не рекомендовал его для 100% сложного кода.
источник
Модульное тестирование научных кодов полезно по ряду причин.
Три, в частности:
Модульные тесты помогают другим людям понять ограничения вашего кода. По сути, модульные тесты являются формой документации.
Модульные тесты проверяют, чтобы убедиться, что одна единица кода возвращает правильные результаты, и проверяют, чтобы убедиться, что поведение программы не изменяется при изменении деталей.
Использование модульных тестов облегчает модульность ваших исследовательских кодов. Это может быть особенно важно, если вы начинаете пытаться нацеливать свой код на новую платформу, например, вы заинтересованы в его распараллеливании или запуске на компьютере с GPGPU.
Прежде всего, модульные тесты дают вам уверенность в том, что результаты исследований, которые вы производите с использованием ваших кодов, являются достоверными и проверяемыми.
Отмечу, что вы упоминаете регрессионное тестирование в своем вопросе. Во многих случаях регрессионное тестирование достигается с помощью автоматического регулярного выполнения модульных тестов и / или интеграционных тестов (которые проверяют правильность работы фрагментов кода при объединении; в научных вычислениях это часто делается путем сравнения выходных данных с экспериментальными данными или результаты более ранних программ, которым доверяют). Похоже, вы уже успешно используете интеграционные или модульные тесты на уровне больших сложных компонентов.
Я хотел бы сказать, что, поскольку исследовательские коды становятся все более сложными и полагаются на код и библиотеки других людей, важно понимать, где возникает ошибка, когда она возникает. Модульное тестирование позволяет намного проще выявить ошибку.
Вы можете найти описание, доказательства и ссылки в Разделе 7 «План ошибок» статьи, которую я в соавторстве написал « Лучшие практики для научных вычислений», - в ней также представлена дополнительная концепция защитного программирования.
источник
В моем deal.II классов я преподаю , что программное обеспечение , которое не имеет тестов не работает правильно (и перейти к стрессу , что я намеренно сказал « ничего не работает правильно», а не " может не работать правильно).
Конечно, я живу по мантре - вот как это делается. Я выполнил 2500 тестов с каждым коммитом ;-)
Более серьезно, я думаю, что Мэтт уже хорошо определяет два класса тестов. Мы пишем модульные тесты для вещей более низкого уровня, и это естественным образом переходит к регрессионным тестам для вещей более высокого уровня. Я не думаю, что смог бы нарисовать четкую границу, которая бы разделяла наши тесты в ту или иную сторону, конечно, есть много людей, которые идут по той линии, где кто-то посмотрел на вывод и нашел его в значительной степени разумным (юнит-тест?) не взглянув на это до последнего бита точности (регрессионный тест?).
источник
И да и нет. Разумеется, тестирование для базовых процедур базового набора инструментов, которые вы используете, чтобы упростить свою жизнь, таких как процедуры преобразования, отображения строк, базовая физика и математика и т. Д. Когда речь идет о классах или функциях вычислений, для них обычно может потребоваться длительное время выполнения, и Вы можете предпочесть тестировать их как функциональные тесты, а не как модули. Кроме того, проводите юнит-тестирование и подчеркивайте те классы и сущности, уровень и использование которых сильно изменятся (например, в целях оптимизации) или чьи внутренние детали будут изменены по какой-либо причине. Наиболее типичным примером является класс, упаковывающий огромную матрицу, отображенную с диска.
источник
Абсолютно!
Что, тебе мало?
В научном программировании больше, чем в любом другом виде, мы разрабатываем, основываясь на попытках подобрать физическую систему. Как вы узнаете, сделали ли вы что-то кроме тестирования? Прежде чем вы начнете кодировать, решите, как вы собираетесь использовать свой код, и отработайте несколько примеров. Попробуйте поймать любые возможные крайние случаи. Сделайте это модульным способом - например, для нейронной сети вы можете сделать набор тестов для одного нейрона и набор тестов для полной нейронной сети. Таким образом, когда вы начинаете писать код, вы можете убедиться, что ваш нейрон работает, прежде чем вы начнете работать в сети. Работа в таких этапах означает, что когда вы сталкиваетесь с проблемой, у вас есть только самый последний «этап» кода для тестирования, более ранние этапы уже были протестированы.
Кроме того, если у вас есть тесты, если вам нужно переписать код на другом языке (например, преобразование в CUDA), или даже если вы просто обновляете его, у вас уже есть тестовые случаи, и вы можете использовать их для Убедитесь, что обе версии вашей программы работают одинаково.
источник
Да.
Идея, что любой код написан без юнит-тестов, анафема. Если вы не докажете, что ваш код верен, а затем докажете, что код правильный = P
источник
Я бы подошел к этому вопросу прагматично, а не догматично. Задайте себе вопрос: «Что может пойти не так в функции X?» Представьте себе, что происходит с выводом, когда вы вводите в код некоторые типичные ошибки: неправильный префактор, неправильный индекс ... И затем пишете модульные тесты, которые могут обнаружить такую ошибку. Если для данной функции нет способа написать такие тесты без повторения кода самой функции, то не надо - но подумайте о тестах на следующем более высоком уровне.
Гораздо более важной проблемой с юнит-тестами (или фактически любыми тестами) в научном коде является то, как справляться с неопределенностью арифметики с плавающей точкой. Насколько я знаю, хороших общих решений пока нет.
источник
Мне жаль Тангурену - где-то здесь мантра «Непроверенный код - это неработающий код», и это пришло от босса. Вместо того, чтобы повторять все веские причины для юнит-тестирования, я хочу просто добавить несколько подробностей.
источник
Я использовал модульное тестирование, чтобы добиться хорошего эффекта в нескольких небольших (то есть в одном программировании) кодах, включая третью версию моего кода для анализа диссертаций по физике частиц.
Первые две версии рухнули под их собственным весом и умножением взаимосвязей.
Другие написали, что взаимодействие между модулями часто является местом, где нарушается научное кодирование, и они правы в этом. Но это намного легче диагностировать эти проблемы , когда вы можете убедительно показывают , что каждый модуль будет делать то , что он хотел сделать.
источник
Немного другой подход, который я использовал при разработке химического решателя (для сложных геологических областей), был тем, что вы могли бы назвать модульным тестированием с помощью копирования и вставки фрагмента .
Создание тестового комплекта для исходного кода, встроенного в модельер большой химической системы, было невозможно осуществить в установленные сроки.
Однако мне удалось разработать все более сложный набор фрагментов, показывающих, как работает анализатор (Boost Spirit) для химических формул, в качестве модульных тестов для различных выражений.
Последний, самый сложный модульный тест был очень близок к коду, необходимому в системе, без необходимости изменения этого кода, чтобы его можно было смоделировать. Таким образом, я смог скопировать код, протестированный на моем модуле.
То, что делает это не просто учебным упражнением и настоящим набором регрессии, - это два фактора: модульные тесты хранятся в основном источнике и выполняются как часть других тестов для этого приложения (и да, они получили побочный эффект от Boost). Дух изменился спустя 2 года) - поскольку скопированный и вставленный код был минимально изменен в реальном приложении, в нем могли быть комментарии, относящиеся к модульным тестам, чтобы помочь кому-то их синхронизировать.
источник
Для больших баз кода полезны тесты (не обязательно модульные) для высокоуровневых вещей. Модульные тесты для некоторых более простых алгоритмов также полезны, чтобы убедиться, что ваш код не делает глупостей, потому что
sin
вместо него используется вспомогательная функцияcos
.Но для всего исследовательского кода очень сложно писать и поддерживать тесты. Алгоритмы имеют тенденцию быть большими без значимых промежуточных результатов, которые могут иметь очевидные тесты, и часто для их запуска требуется много времени. Конечно, вы можете протестировать контрольные прогоны, которые дали хорошие результаты, но это не очень хороший тест в смысле модульного тестирования.
Результаты часто являются приблизительными значениями истинного решения. Хотя вы можете тестировать свои простые функции, если они точны с точностью до некоторого эпсилона, будет очень трудно проверить, является ли, например, какая-то сетка результата правильной или нет, что было оценено визуальным осмотром пользователем (вами) ранее.
В таких случаях автоматизированные тесты часто имеют слишком высокое соотношение затрат и выгод. Я рекомендую что-то лучше: написать тестовые программы. Например, я написал скрипт Python среднего размера для создания данных о результатах, таких как гистограммы размеров ребер и углов сетки, площади наибольшего и наименьшего треугольника и их соотношения и т. Д.
Я могу использовать его для оценки входных и выходных сеток во время нормальной работы и использовать его для проверки работоспособности после того, как я изменил алгоритм. Когда я меняю алгоритм, я не всегда знаю, лучше ли новый результат, потому что часто нет абсолютной меры, какое приближение является лучшим. Но, генерируя такие метрики, я могу рассказать о некоторых факторах, которые лучше похожи на «Новый вариант в конечном итоге имеет лучшее соотношение углов, но худшую скорость сходимости».
источник