Мы работаем исходя из предположения, что непроверенный код не работает, поэтому некоторые тесты для вашей новой специальной функции в порядке, мы разделим их на 3 основные категории:
Спот-тесты состоят из проверки того, что ожидаемое исключение генерируется, когда входы ошибочны (или иным образом генерируют неопределенные значения), и проверки любых специальных значений. Мы можем проверить ожидаемые исключения с помощью BOOST_CHECK_THROW
, например, если это ошибка домена для последнего параметра, который находится за пределами. Диапазон [0,1
тогда у нас может быть:
BOOST_CHECK_THROW(my_special(0, -0.1), std::domain_error);
BOOST_CHECK_THROW(my_special(0, 1.1), std::domain_error);
Когда функция знает точные значения (обычно целые значения), мы можем использовать BOOST_CHECK_EQUAL
BOOST_CHECK_EQUAL(my_special(1.0, 0.0), 0);
BOOST_CHECK_EQUAL(my_special(1.0, 1.0), 1);
Когда функция имеет известные значения, которые не являются точными (с точки зрения плавающей точки), тогда мы можем использовать BOOST_CHECK_CLOSE_FRACTION
BOOST_CHECK_CLOSE_FRACTION(my_special(0.5, 0.5), 2 * constants::pi<double>(), std::numeric_limits<double>::epsilon() * 4);
Если функция реализована каким-либо другим известным хорошим источником (например, Mathematica или это онлайн-версии functions.wolfram.com или www.wolframalpha.com, то неплохо проверить нашу реализацию, имея по крайней мере одно независимо сгенерированное значение для каждой ветви кода, которую может занять наша реализация). Чтобы хорошо сочетать их с нашей системой тестирования, лучше всего сформулировать их следующим образом:
static const boost::array<boost::array<T, 3>, 10> my_special_data = {{
{{ SC_(0), SC_(0), SC_(1) }},
{{ SC_(0), SC_(1), SC_(1.26606587775200833559824462521471753760767031135496220680814) }},
}};
Позже мы увидим, как использовать эту таблицу и значение макроса SC_
. Один важный момент заключается в том, чтобы убедиться, что входные значения имеют точные двоичные представления: поэтому выберите значения, такие как 1.5, 1.25, 1.125 и т. Д. Это гарантирует, что если my_special
необычайно чувствителен в одной области, мы не получим, по-видимому, больших ошибок только потому, что входы по ошибке равны 0,5 ulp.
Мы можем генерировать большое количество тестовых значений для проверки как будущих регрессий, так и накопленной ошибки округления или отмены в нашей реализации. В идеале мы использовали бы для этого независимую реализацию (например, my_special может быть определена непосредственно с точки зрения других специальных функций, но не реализована таким образом по причинам производительности или точности). В качестве альтернативы мы можем использовать нашу собственную реализацию напрямую, но с любыми особыми случаями (асимптотические расширения и т. д.) отключены. У нас есть набор инструментов для генерации тестовых данных напрямую, вот типичный пример:
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/math/tools/test_data.hpp>
#include <boost/test/included/prg_exec_monitor.hpp>
#include <fstream>
using namespace boost::math::tools;
using namespace boost::math;
using namespace std;
using namespace boost::multiprecision;
template <class T>
T my_special(T a, T b)
{
return a + b;
}
int cpp_main(int argc, char*argv [])
{
typedef number<cpp_dec_float<500> > bignum;
parameter_info<bignum> arg1, arg2;
test_data<bignum> data;
bool cont;
std::string line;
if(argc < 1)
return 1;
do{
if(0 == get_user_parameter_info(arg1, "a"))
return 1;
if(0 == get_user_parameter_info(arg2, "b"))
return 1;
bignum (*fp)(bignum, bignum) = &my_special;
data.insert(fp, arg2, arg1);
std::cout << "Any more data [y/n]?";
std::getline(std::cin, line);
boost::algorithm::trim(line);
cont = (line == "y");
}while(cont);
std::cout << "Enter name of test data file [default=my_special.ipp]";
std::getline(std::cin, line);
boost::algorithm::trim(line);
if(line == "")
line = "my_special.ipp";
std::ofstream ofs(line.c_str());
line.erase(line.find('.'));
ofs << std::scientific << std::setprecision(50);
write_code(ofs, data, line.c_str());
return 0;
}
Обычно таким образом генерируется несколько наборов данных, включая случайные значения в некотором «нормальном» диапазоне, экстремальные значения (очень большие или очень маленькие) и значения, близкие к любому «интересному» поведению функции (сингулярности и т. д.).
Мы разделили фактический тестовый файл на 2 отдельные части: заголовок, который содержит тестовый код в виде серии шаблонов функций, и фактический тест-драйвер .cpp, который решает, какие типы тестируются, и устанавливает «ожидаемые» показатели ошибок для этих типов. Это делается таким образом, потому что:
- Мы хотим протестировать как с построенными типами с плавающей запятой, так и с многоточными типами. Тем не менее, как компиляция, так и время выполнения с последними могут быть слишком длинными для людей, которые проводят тесты, чтобы реально справиться с ними, поэтому имеет смысл разделить тест на (по крайней мере) 2 части.
- Определение макроса SC_, используемого в наших таблицах данных, может отличаться в зависимости от типа тестирования (см. ниже). Опять же, это в значительной степени вопрос управления временем компиляции, поскольку большие таблицы определяемых пользователем типов могут занять безумное количество времени для компиляции с некоторыми компиляторами.
Заголовок теста содержит 2 функции:
template <class Real, class T>
void do_test(const T& data, const char* type_name, const char* test_name);
template <class T>
void test(T, const char* type_name);
Перед их реализацией мы включим необходимые заголовки и предоставим определение по умолчанию для макроса SC_:
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/math/special_functions/my_special.hpp>
#include <boost/array.hpp>
#include "functor.hpp"
#include "handle_test_result.hpp"
#include "table_type.hpp"
#ifndef SC_
#define SC_(x) static_cast<typename table_type<T>::type>(BOOST_JOIN(x, L))
#endif
Самая простая функция для реализации — это функция «тест», которую мы будем называть программой тест-драйвера. Он просто включает в себя файлы, содержащие табличные тестовые данные и вызовы функции do_test
для каждой таблицы, а также описание тестируемого:
template <class T>
void test(T, const char* type_name)
{
# include "my_special_1.ipp"
do_test<T>(my_special_1, name, "MySpecial Function: Mathematica Values");
# include "my_special_2.ipp"
do_test<T>(my_special_2, name, "MySpecial Function: Random Values");
# include "my_special_3.ipp"
do_test<T>(my_special_3, name, "MySpecial Function: Very Small Values");
}
Функция do_test
берет каждую таблицу данных и вычисляет значения для каждой строки данных, наряду со статистикой для максимальной и средней ошибки и т. д., большая часть этого обрабатывается некоторым кодом boilerplate:
template <class Real, class T>
void do_test(const T& data, const char* type_name, const char* test_name)
{
typedef typename T::value_type row_type;
typedef Real value_type;
typedef value_type (*pg)(value_type, value_type);
#if defined(BOOST_MATH_NO_DEDUCED_FUNCTION_POINTERS)
pg funcp = boost::math::my_special<value_type, value_type>;
#else
pg funcp = boost::math::my_special;
#endif
boost::math::tools::test_result<value_type> result;
std::cout << "Testing " << test_name << " with type " << type_name
<< "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
result = boost::math::tools::test_hetero<Real>(
data,
bind_func<Real>(funcp, 0, 1),
extract_result<Real>(2));
handle_test_result(result, data[result.worst()], result.worst(), type_name, "boost::math::my_special", test_name);
}
Теперь нам просто нужно написать программу тест-драйвера, на самом базовом она выглядит примерно так:
#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/tools/test.hpp>
#include <boost/math/tools/stats.hpp>
#include <boost/type_traits.hpp>
#include <boost/array.hpp>
#include "functor.hpp"
#include "handle_test_result.hpp"
#include "test_my_special.hpp"
BOOST_AUTO_TEST_CASE( test_main )
{
test(0.1F, "float");
test(0.1, "double");
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
test(0.1L, "long double");
#ifndef BOOST_MATH_NO_REAL_CONCEPT_TESTS
#if !BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582))
test(boost::math::concepts::real_concept(0.1), "real_concept");
#endif
#endif
#else
std::cout << "<note>The long double tests have been disabled on this platform "
"either because the long double overloads of the usual math functions are "
"not available at all, or because they are too inaccurate for these tests "
"to pass.</note>" << std::cout;
#endif
}
Это почти все, что есть - за исключением того, что если вышеупомянутая программа запущена, очень вероятно, что все тесты потерпят неудачу, поскольку по умолчанию максимально допустимая ошибка составляет 1 эпсилон. Таким образом, мы определим функцию (не забудьте назвать ее с самого начала test_main
выше), чтобы увеличить пределы чего-то разумного, основываясь как на функции, которую мы вызываем, так и на конкретных тестах плюс платформа и компилятор:
void expected_results()
{
const char* largest_type;
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
if(boost::math::policies::digits<double, boost::math::policies::policy<> >() == boost::math::policies::digits<long double, boost::math::policies::policy<> >())
{
largest_type = "(long\\s+)?double|real_concept";
}
else
{
largest_type = "long double|real_concept";
}
#else
largest_type = "(long\\s+)?double";
#endif
add_expected_result(
".*",
".*",
".*",
largest_real,
"MySpecial Function:.*Small.*",
"boost::math::my_special",
50,
20);
}
Тестирование многоточного типа проводится тест-драйверами в libs/multiprecision/test/math, см. Обратите внимание, что эти тесты выполняются только профессионально, поскольку для их создания и запуска требуется много циклов процессора.
Как отмечалось выше, эти тестовые программы могут занять некоторое время, поскольку мы создаем множество шаблонов для нескольких различных типов, и наши тестовые бегуны уже растянуты до предела и, вероятно, используют устаревшее «запасное» оборудование. Есть две вещи, которые мы можем сделать, чтобы ускорить процесс:
- Используйте предварительно составленный заголовок.
- Используйте отдельную компиляцию наших шаблонов специальных функций.
Мы можем внести эти изменения, изменив список из:
#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/tools/test.hpp>
#include <boost/math/tools/stats.hpp>
#include <boost/type_traits.hpp>
#include <boost/array.hpp>
#include "functor.hpp"
#include "handle_test_result.hpp"
Чтобы просто:
#include <pch_light.hpp>
И меняться
#include <boost/math/special_functions/my_special.hpp>
Для:
#include <boost/math/special_functions/math_fwd.hpp>
Цель Jamfile, которая строит программу тестирования, будет нуждаться в целях
test_instances
добавление к списку зависимостей источника (см. пример Jamfile).
Наконец, для реализации проекта в libs/math/test/test_instances потребуется изменение функции my_special
.
Эти изменения должны быть сделаны последними, когда my_special
стабилен и код находится в Trunk.
Наши концептуальные проверки подтверждают, что реализация вашей функции не делает никаких предположений, которые не требуются нашими концептуальными требованиями Реальное число . Они также проверяют различные распространенные ошибки и ловушки программирования, в которые мы попали с течением времени. Чтобы добавить свою функцию в эти тесты, отредактируйте libs/math/test/compile_test/instantiate.hpp, чтобы добавить вызовы к вашей функции: есть 7 вызовов к каждой функции, каждый с другой целью. Ищите что-то вроде «ibeta» или «gamm_p» и следуйте их примеру.