![>](style_images/1/nav_m.gif)
![]() |
Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
|
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.137.170.228] |
![]() |
|
![]() |
|
|
Хотел бы научиться тестировать программы. Посоветовали https://pro-prof.com/archives/1549. Решил начать с тестирования функции нечеткого сравнения. Прописал в проекте директории boost, но программа выдает много ошибок (как я понимаю из-за неподключенных библиотек boost, хотя директория с библиотеками прописана в проекте). Помогите, пожалуйста, правильно настроить проект. Проект прикрепил.
Прикреплённый файл ![]() |
Сообщ.
#2
,
|
|
|
Забыл вставить в начало cpp-файла
![]() ![]() #define BOOST_TEST_MAIN #define BOOST_TEST_DYN_LINK |
Сообщ.
#3
,
|
|
|
Хотел бы узнать мнение специалистов относительно преимуществ (недостатков) тестов boost и google. Что выбрать для тестирования программ на С++?
|
![]() |
Сообщ.
#4
,
|
|
Мнение специалиста – не использовать стороннее. Из всех движков самым гибким оказался наш собственный. Остальные слишком топорные.
Добавлено P.S. По большому счёту модульные тесты не становятся лучше/понятнее/проще/итп нужное подчеркнуть от того, что ты используешь готовые движки вместо того, чтобы вообще обойтись без любого из них, а тупо писать свои чеки. |
Сообщ.
#5
,
|
|
|
Понятно.
|
![]() |
Сообщ.
#6
,
|
|
P.P.S. Сравни понятность кода из твоей ссылки с, например, таким:
![]() ![]() tt_printf(PortBuffer, "\n\n" "/******************************* TEST_STEP_03 *****************************/\r" ); /* Set inputs */ fstat_called = fcntl_called = 0; fstat_return_value[0] = 0x7FFFFFFF; fstat_return_value[1] = -1; /* Execute function under test */ ret = dup2(1, 2); /* Check results */ CompareTestStepResult(fstat_called, 2, cEQ, "fstat() is called 2 times"); CompareTestStepResult(fstat_1st_param[0], 1, cEQ, "1st param of 1st call of fstat() is correct"); CompareTestStepResult(fstat_1st_param[1], 2, cEQ, "1st param of 2nd call of fstat() is correct"); CheckTestStepResult ("2nd param of 1st call of fstat() is not NULL", fstat_2nd_param[0] != NULL); CheckTestStepResult ("2nd param of 2nd call of fstat() is correct", fstat_2nd_param[0] == fstat_2nd_param[1]); CompareTestStepResult(fcntl_called, 0, cEQ, "fcntl() isn't called"); CompareTestStepResult(ret, -1, cEQ, "dup2() returns the -1"); |
Сообщ.
#7
,
|
|
|
Читается проще, но все равно мне не очень понятно. Как я понял, далее ret сравнивается с значениями массива fstat_return_value, но непонятно, почему в качестве дескрипторов файлов передаются 1 и 2. Видимо, чтобы это понять, нужно знать особенности теста. Хотел бы также спросить, что значить "застабированы"? Я правильно понял, что они постоянны (stable)?
|
![]() |
Сообщ.
#8
,
|
|
Цитата tuchin @ А почему по твоей ссылке квадратное уравнение тестируется набором (2, 7, 3)? Это просто некие значения входов, попадающие в нужный диапазон классов эквивалентности. Как я понял, далее ret сравнивается с значениями массива fstat_return_value, но непонятно, почему в качестве дескрипторов файлов передаются 1 и 2. Цитата tuchin @ Безусловно. Это один кейз из общего числа в 22 штуки. Результат его работы, если интересно:Видимо, чтобы это понять, нужно знать особенности теста. ![]() ![]() /******************************* TEST_STEP_03 *****************************/ *** Test Step: fstat() is called 2 times :2: <EQ> :2: *** PASSED *** *** Test Step: 1st param of 1st call of fstat() is correct :1: <EQ> :1: *** PASSED *** *** Test Step: 1st param of 2nd call of fstat() is correct :2: <EQ> :2: *** PASSED *** *** Test Step: 2nd param of 1st call of fstat() is not NULL *** PASSED *** *** Test Step: 2nd param of 2nd call of fstat() is correct *** PASSED *** *** Test Step: fcntl() isn't called :0: <EQ> :0: *** PASSED *** *** Test Step: dup2() returns the -1 :-1: <EQ> :-1: *** PASSED *** Вообще, если действительно есть желание серьёзно заняться тестированием, выбор движка не так важен, гораздо важнее заняться теорией. Квадратное уравнение во ссылке, например, протестировано очень плохо. По факту тот тест ничего не доказывает. С другой стороны, неудачный движок вынудит тратить много сил на себя вместо дела. Например, используя CppTest, мы тестировали в среднем 2 строки в час, с нашим движком легко получалось 5. Удивлён малой скоростью? Это потому, что хорошее тестирование способно не подтвердить, а доказать с заданной заказчиком сигмой. У того теста есть подтверждение формальному следованию описания поведения, но никакой доверительной силы он не предоставил. Цитата tuchin @ Нет, они stabbed. Т.к. лежат в отдельной единице трансляции, то легко могут быть заменены на собственные, чьё поведение определяется так, как удобно тесту:Хотел бы также спросить, что значить "застабированы"? Я правильно понял, что они постоянны (stable)? ![]() ![]() int fstat(int fd, struct stat *sbuf) { if (fstat_called < MAX_FSTAT) { fstat_1st_param[fstat_called] = fd; fstat_2nd_param[fstat_called] = sbuf; } return fstat_called++ < MAX_FSTAT ? fstat_return_value[fstat_called-1] : -1; } int fcntl(int fd, int cmd, ...) { va_list va_param; va_start(va_param, cmd); ++fcntl_called; fcntl_1st_param = fd; fcntl_2nd_param = cmd; fcntl_3rd_param = va_arg(va_param, int); va_end(va_param); return fcntl_return_value; } Добавлено P.S. Мои слова не стоит воспринимать как призыв плюнуть на движки и писать своё. Нравится boost, на здоровье. Просто в них обычно тесно, и доказательной силы достигнуть непросто. Просто чтоб донести, что тестирование – это наука, и хороших верификаторов мало. ВУЗы их не готовят, т.к. спрос на них невысок, обычно за пределами высококритичного ПО они не особо востребованы. |
Сообщ.
#9
,
|
|
|
Спасибо за подробный ответ, который мне помог понять, что тестированием надо заниматься профессионально и обладать большим багажом знаний. Просто попробовать и поиграться, прочитав про TDD и его значение - не получится.
|
![]() |
Сообщ.
#10
,
|
|
Ну, прям-таки профессионально, возможно, смысла нет, на это отдельные кадровые должности есть. Но иметь представление о хорошем тестировании любому программисту будет очень полезно. Хотя бы потом тыкать коллегам репорты с воплями "у меня всё путём, баги у вас!"
![]() |
![]() |
Сообщ.
#11
,
|
|
Небольшой ликбез, надеюсь, не будет сильным оффтопом тут. Давай на примере, того же квадратного уравнения. Как написать хороший тест. Не идеальный, но хороший.
Анализ ТЗ выявляет вычисление дискриминанта как отдельный подалгоритм. Почему? Потому что наш алгоритм имеет три входа a, b и c, которые свои классы эквивалентности не делят на диапазоны, однако они зависят от дискриминанта, который имеет три диапазона. Следовательно не тестировать D огромная ошибка, т.к. иначе корректность решения (в ТЗ трёхветвенное) не будет показано. Анализ подалгоритма тут же выявляет проблему: решение D = 0 нетестируемо. В принципе. По двум причинам: В итоге открываем СП (Сообщение о Проблеме) на нетестируемость и избыточность логики. Если заказчик его отклоняет, мол, D = 0 можно получить и ненулевыми c и b, отвечаем, что по Стандарту IEEE-754 (IEC 60559) получить точное вещественное значение возможно, только подготавливая битовое его представление явным образом, чего невозможно добиться вычислениями, только явным заданием битового образа, что невозможно для D. Тестовые же сценарии, которые могут тестировать или то, или другое в зависимости от фазы Луны в момент коммита в репозиторий сырцов компиляторового оптимизатора, никому не интересны, их результатом будет нечто недетеримированное, и в итоге их всё равно что нет, т.к. их результаты всё равно нужно будет не учитывать. Но если и после этого его будет устраивать вариант, что вектор с одним элементом он на практике никогда не будет получать, это будет его проблема, СП должно быть в базе заказчика с его по нему решением и его обоснованием, и пусть сам отдувается перед инвесторами за системные требования к программному комплексу и стоимости его разработки и саппорта. Тестируем диапазон D > 0. Берём первые подходящие a, b и с. Но дробные, т.к. целочисленные непоказательны, если только мы не решаем квадратное уравнение на кольце целых чисел, что никак не следует из ТЗ и скорее ему даже противоречит. А то вдруг некий умник параметры у solve_quadratics() проставил long long. Вообще, при тестировании всегда нужно готовым к тому, что у тебя будет только набор .o, сырцов никто не даст. И с чем там линкуется твой тест, только линкеру и известно, та и у того все типы уже стёрты. Итак, какие же (a, b и с) взять? Если D можно получить в явном виде, то так и надо делать, и можно брать любую тройку чисел. Но в нашем случае это не вариант. Значит нужно сделать его значение максимально прозрачным. Берём тройку (0.5, 0, -1.234). Во-первых, это избавляет от влияния b и как-то устраняет деление на 2a. Правда, оставляет √, но тут уж никак. В итоге мы должны получить корни (1.570987, -1.570987). Имеет смысл ограничить разрядность чека результатов, это предмет обсуждения с заказчиком, а не взятия наобум пальцем с потолка, даже если это std::numeric_limits<double>::epsilon(). (И даже тем более, если кроме std::numeric_limits<double>::epsilon() с потолка взять больше нечего.) Чего в ТЗ не было. Ещё СП в базу. Просто потому, что потеря точности в вычислениях плавающей точки штука очень неприятная, и отвечать за неё архитектору, а не программисту и тем более верификатору. Дело верификатора ткнуть пальцем в репорт, а не оправдываться, с чего это он решил, что тут это проблема. Это был первый кейз. А какой второй? Правильно: проверяем влияние c: (0.5, 0, -1.432) с итогом (1.692336, -1.692336). Вообще говоря, для нормального теста этого влияния достаточно, чтобы прочекать зависимость логики от входа. Тем более, зависимость D от c линейна. Однако мы наблюдаем не D, а √D, а эта зависимость квадратична, поэтому (сорри, без объяснения теории) интерполируется полиномом второй степени, значит нужен третий кейз из этого же диапазона класса эквивалентности. Подойдёт любой с теми же a и b, почему бы и не (0.5, 0, -14.32) с итогом (5.351635, -5.351635). Настала очередь, ну например, a: (0.567, 0, -14.32) / (5.025508, -5.025508). Тут уже влияния деления на 2a не избежать, однако это влияние всё ещё довольно прозрачно. И снова мы в ТЗ наблюдаем квадратичную зависимость из-за √D, так что нужен третий кейз для a. Да, это был второй, т.к. первым был... э-э-э, ну, предыдущий, который третий для с. И тут сюрприз: (-0.567, 0, -14.32) / (). Естественно, ведь сменой знака a мы сменили знак дискриминанта и ушли в другой диапазон класса эквивалентности. Ну так никто и не говорил, что так нельзя. Зато следующий кейз (-0.567, 0, 14.32) / (-5.025508, 5.025508) всё ставит на свои места. Во-первых, мы чекнули решение по диапазонам, а во-вторых, продолжаем чекать D максимально прозрачно с чёткой демонстрацией зависимостей подалгоритма от входов. (Тут букмарк.) Ну и осталось проверить только b: (1, 2.345, 0) / (0.000000, -2.345000). И снова делаем D максимально прозрачным: 4ac равно нулю, так что b видно невооружённым глазом – он удваивается со знаком минус из-за -b перед √ и делится на удвоенную же 1 == а. Увы, но это только первый кейз, т.к. отличается от любого предыдущего не только по b. Второй кейз для b: (1, 3.456, 0) / (0.000000, -3.456000). Не удивительно, правда? А нужен ли третий? На самом деле b его не требует, т.к. его зависимость линейна, ибо его квадрат нейтрализуется квадратным корнем. Однако этого требует сам D, т.к. ни в одном из кейзов влияние всех трёх входов одновременно не было задействовано. Последний: (1, 3.456, 1) / (-0.318751, -3.137249). (Тут ещё букмарк.)Теперь вычисление D > 0 полностью покрыто кейзами. В качестве бонуса также прочекано наличие решение по знаку D. Но не протестировано. Самое время заняться. Но сначала ремарка. Каждый из входов уже прочекан дробным, поэтому моё замечание касательно обязательной нецелочисленности выполнено. Не следует тестировать то, что уже протестировано. Многократные чеки тест лучше не делают. Но тут важно понимать, чем отличается многократное тестирование от достаточного и в случае недостаточности требующего дополнительных чеков. Пример с тремя кейзами по a и c вместо двух хороший пример второго, но для тестирования нецелочисленности типов одного чека достаточно. Ну и на предмет второго букмарка. Ты, возможно, заметил, что от кейза к кейзу тестируемый вход меняется только один, остальные стоят в прежних значениях. Это очень крутой метод, помимо прочего влёт находящий самые страшные баги прогаммеров: ошибки копипасты. Даже если ты их специально не ищешь. Даже когда у тебя нет сырцов. У принципа парности – для любого сценария должна быть пара в лице сценария, отличающегося от него одним и только одним входом, и эта разница в значении входа видна на выходах алгоритма – важное преимущество: ты доказываешь, что конкретные входы действительно подключены к правильным точкам алгоритма. Но в последнем сценарии это правило нарушено. И тем не менее для хорошего теста этого достаточно, т.к. для любого входа a, b и c влияние наличествует. Вот идеальный бы включил ещё один кейз, где менялся б ещё и a, но это в общем-то опять же предмет обсуждения с заказчиком: нужно ли рассматривать по принципу парности все входы или только явно выставляемые. D неявно выставляем, он является выходом подалгоритма и он же вход в основной алгоритм, поэтому он предмет отдельного разговора при обсуждении ТЗ. Продолжение следует... |
![]() |
Сообщ.
#12
,
|
|
Продолжение.
За сим можно и закрыть тест подалгоритма. Мы чётко проверили влияние каждого входа, показали, что каждый из них по отдельности влияет на результат ожидаемым образом. Что касается тестирования решения, то тут всё довольно просто. Меняем a (заодно и закроем второй букмарк, ведь мы же хороший тест пишем, правда?) так, чтобы D > 0 было максимально близко к нулю. Например, (2.98598, 3.456, 1) с результатами (-0.578035, -0.579374). И теперь по-хорошему, если бы работали в целочисленной арифметике, то в следующем кейзе следовало бы приравнять D к нулю. Но т.к. ненадёжных сценариев мы не пишем, то нафик, СП уже написано. Так что сразу переходим к D < 0, меняя c... ну, почему бы лишний раз не подтвердить его влияние. Ну и дробность заодно. Замечу, это не повторное тестирование, это просто так вышло. Настоящая цель кейза в тестировании границ диапазонов класса эквивалентности D. Нижнюю границу одного мы чекнули в прошлом кейзе, а тут будет верхняя другого. А влияние и дробность c в нём суть просто бонус, его даже документировать не стоит. Итак, (2.98598, 3.456, 1.000001) / (). Вуаля, если раньше мы лишь проверили наличие решения, то тут мы его полностью протестировали. Замечу, что в отличие от теста по ссылке тут мы имеем набор тестовых сценариев, вполне себе доказывающий, а не демонстрирующий. Мы проверили решение по < или > вблизи нуля на узком диапазоне допустимых воздействий и с точностью до оговоренных с заказчиком погрешностей... ну да, тут они взяты с потолка, 7 десятичных знаков мантиссы, но мы же открыли СП, ведь правда? ...подтвердили корректность его реализации в коде. Осталось совсем немного: собственно протестировать расчёт корней, имея D в качестве входа. Не a, b и c, а a, b и D. К сожалению, D ставится косвенно с использованием тех же входов а и b, что и D, так что опять придётся пытаться соблюсти максимальную прозрачность. Итак, кофейная гуща подсказала первый кейз: (-12.345, 23.456, 34.567) с результатами (-0.974199, 2.874239). А дальше по накатанной: меняем c с целью сменить значение D, тестируя т.с. влияние конкретно D. (-12.345, 23.456, 45.678) / (-1.195361, 3.095401). Не забываем о квадратичной зависимости корней от D, поэтому ещё один кейз: (-12.345, 23.456, 56.789) / (-1.395763, 3.295804). Осталось проверить влияние остальных входов. И тут мы понимаем всю прелесть того, что ранее мы прозрачно протестировали D. А именно: мы можем влиять на его значение посредством смены c — входа, который не используется в расчёте корней. Следовательно, т.к. подалгоритм расчёта D полностью протестирован, и его корректность не просто продемонстрирована, а вполне себе доказана, мы имеем полное право менять сразу два входа: один тестируемый на предмет влияния на расчёт корней и ещё c, чтобы оставить D неизменным. По факту тут парность сценариев относится уже к a, b и внутреннему входу D, а не внешнему c. Теперь мы знаем, что делать: (-12.345, 34.567, 43.73324) / (-0.945743, 3.745824) для b и (-23.456, 34.567, 23.017) / (-0.497749, 1.971444) для a. Т.к. зависимость от b линейна, для него достаточно пары сценариев, и один уже есть в качестве ему предшествующего. А вот зависимость от a нелинейна, она гиперболична. С учётом того, что гипербола суть тоже кривая второго порядка, нужен ещё один кейз со сменой a: (-45.678, 34.567, 11.8194) / (-0.255598, 1.012352). На этом всё. Хороший тест завершён. 17 сценариев. Можно ещё 18-ым добавить (-45.678, 0, 0) / (0.000000), чтобы дособрать покрытие по неразумному решению D = 0. Если тебе кажется, что это дохрена, то нет. Для совсем хорошего теста нужно ещё чекнуть баундари, т.е. MIN/MAX по модулю и обоих знаков, причём отдельно для каждого входа. Причём ещё и в тестировании подалгоритма. И ещё в тестировании решения. Засада в том, что MIN/MAX должны быть указаны в ТЗ, а не в Стандарте языка или даташите на процессор, т.к. в рамках конкретных задач они отнюдь не обязаны быть физическими MIN/MAX. Ещё СП в базу. Также в рамках отказоустойчивости нужно проверить спец.форматы в лице NaN, INF, денормализованных, кое-где нуля и вот тут уже да, физические MIN/MAX, которые обычно выходят за рамки логических MIN/MAX из ТЗ. Но тут всё ещё сложнее, т.к. отказоустойчивость суть очень предметный разговор с заказчиком, без чего ТЗ подписывать вообще никогда не стоит. Вот так получится очень хороший тест. На вскидку в 35-40 сценариев. Может быть меньше: при тестировании границ, денормализованных и нулей парности не требуется, если влияние входов отдельно протестировано ранее. Но будет очень сложно чекать баундари параллельно, т.к. при установке входов в граничные значения входов придётся не допускать переполнений и антипереполнений в вычислениях. Зато так выявляются тонкие места в логике оптимизатора компилятора, который запросто может переставить операции как ему удобно и вызвать выходы за границы в промежуточных вычислениях, где не ждали. Сравни этот набор сценариев с тестом по ссылке с его тремя (!) сценариями. Те три сценария не доказывают ничего, это тестирование методом Монте Карло. Описанный же тут подход имеет доказательную силу, т.к. демонстрирует эволюцию заказанной мат.модели в фазовом пространстве входов. Я не знаю, какая тут получится сигма, лень считать полиномы, но с большой долей вероятности две должны получиться будут. В подавляющем количестве случаев на практике этого достаточно. Ну что, 5 строк в час уже не кажутся слишком малой скоростью? ![]() Домашнее задание: найти косяк в районе первого букмарка. Если не получится, ничего страшного, начинающие (и не только) верификаторы его часто допускают. Вот если его не заметит ревьюер, то ой. Наверное, ты хочешь спросить, если хороший тест укладывается в 17 сценариев, а в 35 очень хороший.... что же тогда за зверь под названием "идеальный тест"? Очень хороший вопрос. Окончание следует. |
![]() |
Сообщ.
#13
,
|
|
Окончание.
Я забыл упомянуть один важный аспект при тестировании решений. Помимо того, что корректность реализации решения должно быть протестировано, должно быть также протестировано и вычисление каждого условия в этом решении. В нашем случае решения два, но в каждом только одно условие. Но если решение состоит из комбинации условий, то тестированию подлежит каждое независимо друг от друга. Например решение (a>=b && c!=d) состоит из двух условий, и тестированию подлежит как первое (в общем случае) тремя сценариями — a=b+ε, a=b и a=b-ε — так и второе: выставив a>=b, проверяем c=d+ε, c=d и c=d-ε. Причём не надо забывать, что показано должно быть влияние каждого входа. Например для целочисленных a и b можно выбрать векторы (13, 14), (13, 13) и (14, 13), а не (13, 12). Сразу замечу, что идеальных тестов не пишут, как правило. Они очень дороги, и заказчик не видит для себя выгоды в том, чтобы оплачивать идеальность с получением в обмен весьма небольшого профита. Но это не значит, что они не востребованы вообще, в ряде случаев их привлекают для решения очень специфических задач, например, для исследования защищённых шелезяк, который как только попробуешь их хакнуть, они такие бдыщь! и разряжают в себя кондёр. Так что этот пост призван скорее показать теоретическую силу тестирования как научного метода. На самом деле идеальный тест не очень-то и будет в итоге отличаться от очень хорошего, разница в основном в том, что наборы входов не берутся с потолка, а определяются, включая и их количество, формальными методами. А стоимость его разработки определяется трудоёмкостью применения этой процедуры. Предположим, у нас есть алгоритм, заданный функцией N переменных. В нашем случае функция не очень сложна, и N == 3. Первым делом определяемся со степенью достоверности, которой нужно достичь, и логическими диапазонами входов. Без этого никак. Для двух сигм достоверность где-то около 95%, например. Фиксируем все входы, кроме одного, и полученную новую функцию одной переменной табулируем в [MIN...MAX] с шагом, покрывающим этот диапазон в количестве точек, соответствующим выбранной достоверности. Для 95%, например их будет 20 штук. Берём частную производную нашей функции по этому входу (ничем не отличается от производной новой функции) и исследуем её на особые точки. Экстремумы и перегибы не особо волнуют, но иногда будут полезны, а вот разрывы и неопределённости очень. Исключаем их из нашего набора и получаем таблично заданную функцию. Это первый шаг. Второй. Берём любой интерполяционный полином и пытаемся выразить им нашу таблицу. Начинаем с двух точек, тупо минимум/максимум, т.е. полинома первой степени. Линейные функции на ура им интерполируются, поэтому на этом и закончим, но для более интересных функций конечно же линейная интерполяция будет почти наверняка плоха. Уровень качества интерполяции определяется посредством вычисления значений полинома в узлах табулирования, в нашем случае в 20 точках. В каждой из них относительная погрешность от табличных значений не должна превышать уровень выбранной достоверности, в нашем случае 5%. Если нашлась хотя бы одна такая, значит степень полинома недостаточна. Выбираем ещё одну, самую "плохую" точку и добавляем её к полиному, т.с. увеличивая его степень. Для получившегося квадратичного полинома повторяем процедуру... и продолжаем до тех пор, пока во всех 20 узлах не получим приемлемое качество интерполяции. Иногда, весьма нечасто, для этого проходится поднимать степень полинома до упора, т.е. до количества узлов. Понятное дело, выше 20-ой степени она не поднимется, но в подавляющем числе случаев она будет невысокой, всё-таки функции обычно довольно гладкие. Предположим, мы получили приемлемую интерполяцию на 5 промежуточных узлах, т.е. степень полинома поднялась до 6-ой. Значит ровно столько (5) тестовых воздействий нам и нужно реализовать, причём именно в тех точках, которые служили узлами интерполяции. Т.к. 2 из них – это границы, то они тоже будут в числе тестовых воздействий, но это будут баундари-сценарии. Шаг второй с половиной, опциональный. Он требуется, если были найдены особые точки. В этом случае они требуют дополнительного исследования. На самом деле ничего сложного или чего-то, чего раньше не делали, нет. Просто принимаем за одну из границ MIN или MAX саму особую точку, а за другую ближайшую нормальную, и просто повторяем шаг два на этом диапазоне с единственным исключением: сама особая точка исключается. Обычно это добавляет пару-тройку сценариев вблизи особой точки с каждой стороны от неё. Шаг третий. Повторяем второй шаг для каждого входа. ![]() Шаг четвёртый, самый трудоёмкий. Проверяем качество полиномов для каждого входа, заменяя значения ранее фиксированных входов на значения, полученные по их полиномам. По каждому узлу, отобранному в качестве входных воздействий. Для всех таких их комбинаций друг с другом. Для этого придётся составлять новые таблицы. Обычно этот шаг просто контрольный, но иногда это приводит к необходимости поправить один-два полученных на шаге 2 сценария. Это следует делать, если значение входа меняется более чем на величину, определяемую выбранной достоверностью, иначе можно оставить как есть. Самое неприятное – редко, но такие правки могут повлечь за собой смещение особых точек, но что делать. Шаг четвёртый с половиной, опциональный. Применяется, если на шаге 4 что-то в сценариях менялось. Повторяем шаг 4, пока ни один из сценариев не будет требовать изменений. Шаг пятый. Самый неоднозначный и сложный для качественной реализации. Вспоминаем про экстремумы и перегибы. Перегибы, впрочем, интересуют только те, в который первая производная близка к нулю. Для каждого входа изучаем поведение других алгоритмов, которые используют выход нашей функции как свой вход. Выбираем сценарий для этого входа с ближайшим значением и пару соседних и, применяя процедуру из шага 2, проверяем, что все эти другие алгоритмы в каждой точке сохраняют точность, соответствующей выбранной достоверности. Если это не так, то на каждую такую ситуацию создаём пару сценариев и демонстрируем потерю точности. Скажем, если выбрали точность 7 знаков мантиссы, но погрешность вычисления не способна сохранить более 5, это надо показать. Обычно это бывает при вычитании двух близких значений или при многократно (в цикле, например) повторяющемся сложении большого и малого значения. Создаём СП о потенциальной потере точности мат.модели в обнаруженных областях значений входов. Шаг шестой. Выкидываем полиномы, таблицы и производные, они больше не нужны. Пишем тест на основе полученных векторов входов, как будто он просто очень хороший. Замечу, это всё не касается проверки решений и их условий, это касается только проверке ...м-м-м, формул, скажем так. Логика же – циклы там, ветвления, влияние входов в правильных точках модели итп – тестируются как обычно. Чего в итоге можно т.о. достигнуть. Не имея на руках ничего, кроме формального описания поведения кода на математическом языке, мы подтвердили (ну или опровергли, бывает) с заданной заранее степенью достоверности корректность реализации, каковой бы она ни была, в заданной области определений, каковая была оговорена. Никто не заставляет программера писать код ровно так, как описано в модели, он имеет право её изменять по своему усмотрению как угодно, например, с целью оптимизации вычислительных ресурсов или задействования специфических для исполнительной платформы средств. Единственное условие – реализованное поведение обязано совпадать с ожидаемым. Используя численные интерполяционные методы, мы абстрагировались от описанной модели и изучили общий случай, нашли самые неприятные для абстрактной модели точки и именно их и включили в сценарии. Также мы изучили поведение мат.модели и нашли узкие места, о чём и сообщили заказчику, так что теперь его задача проанализировать ситуацию и решить, насколько важно избавиться от той или иной потенциальной проблемы потери точности или выпасть в осадок от попадания в точку разрыва. Такие исследования заказчик может сделать и самостоятельно, но во-первых, без натурных испытаний принимать решения по ним крайне неудобно, во-вторых, исследование мат.моделей само по себе непростое занятие, поэтому дополнительное ревью не повредит в любом случае. При этом эта методика гибка и способна масштабироваться. После анализа результатов можно рассматривать их как предварительные и в последующем заказать более тонкое исследование, где достоверность поднята, к примеру, до 99%, что несколько меньше трёх сигм. Да, точек станет 100 вместо 20, степени тоже возрастут, шаги 4 и 5 возрастут в геометрической прогрессии, но если надо, то можно. На практике идеальные результаты востребованы разве что в глубоких научных исследованиях. Ну, т.е. теоретических. Описанный тут процесс, если его применить к описанному ранее тесту, будет на полпорядка дороже в разработке, но достигнет тех же двух сигм и будет иметь, скорее всего, такое же количество сценариев, разве что значения входов будут другими. На практике хорошие и очень хорошие тесты обычно справляются также, но дешевле. Хотя бывает по-разному ...в медицине, например, или там в предварительных исследованиях новых гормональных препаратов, предшествующих натурным испытаниям на животных ![]() Спасибо за внимание. P.S. Тест, законченный в прошлом посту, занимается только квадратным уравнением. Что касается линейного и вырожденного случая, то с ними всё гораздо проще. Рекомендую спроектировать тестовые наборы самостоятельно, просто чтоб пощупать хорошее тестирование, без которого высококритичное ПО просто не будет удовлетворять отраслевым стандартам. Вместо посредственного, принятого в бизнесе. |
Сообщ.
#14
,
|
|
|
Не ожидал, что на свой вопрос получу такой развернутый и интересный ответ. Хорошо бы в какой-нибудь книге или обширной статье были продемонстрированы пошагово процессы создания хороших тестов для интересных задач (хотя бы и для квадратного уравнения) с кодом, с графиками, результатами расчета и т.д. по указанной выше методике. Сам я попытаюсь по этой методике построить тест для квадратного уравнения, но у начинающего изучать программирование на C++ и тестирование это займет немало времени. Спасибо большое за подробные объяснения и потраченное время на ответы!
|
![]() |
Сообщ.
#15
,
|
|
Книг не посоветую. Статей тоже не видел. Спецом не искал, правда, но что-то мне подсказывает, что подобная литература просто не востребована в связи с узкой специализацией, на ней гонорары не наберёшь. Любая доступная широкому кругу лиц литература будет рассматривать виды тестирования и методики их реализаций с точки зрения бизнеса. Бизнес – это бизнес, когда стоимость тестирования и затрачиваемое на него время начинают превышать стоимость и сроки разработки, бизнес банкротится. Или сам, или конкурентами, которые за это время выпустили дешёвый глючный продукт, но захватили рынок. А баги, от которых никто не умер, пофиксали в следующем релизе, заюзав потребителей в роли эдаких верификаторов. Только высококритичное ПО не может позволить себе экономить на верификации, потому что... ну, умирают, бывает.
Добавлено Скажем так. Если б по методам отраслевых стандартов высококритичного ПО выпускалось бы всё ПО, мы бы никогда не теряли документы в МСОффисе, игры никогда б не ломали наши сэйвы, ОСи никогда б не хакались итп. Только вместо условных $60 согласны бы мы были платить $6000 за какой-нить Киберпанк2077 и ждать до 2042 года? Так что не стоит удивляться, что некий отдельно взятый МС21-300 разрабатывается 15 лет и стоит дороже, чем если б его отлили целиком из плутония. |
Сообщ.
#16
,
|
|
|
Спасибо за объяснения, понятно.
|