В основном я пользуюсь boost::test. Это в принципе неплохой фреймворк, но не так давно, занимаясь хобби проектиком подумал... Последнее время для себя все больше пишу на новом C++, и часто получается так, что boost тащится только ради тестирования. И более того, если хочется чего-то необычного - докумментация boost::test не дает ответов на эти вопросы. Нужно лезть в исходники и разбираться, например как вызвать все тесты из своего main? Как в один тест запихнуть несколько разных параметров?
Закончилось все тем, что решил написать свой фреймворк, сблекджеком и ш.. параметризованными тестами и сравнением коллекций...
Первый вопрос - можно ли реализовать синглтон в инклюде. Это необходимо во фреймворке, для того, чтобы тесты из всех исходников регистрировались в одной коллекции. А создавать фреймворк в виде библиотеки ну очень не хотелось.
Синглтон от GoF требует .сpp файл для описания статического указателя - не вариант. А вот синглтон Майерса - прекрасно живет в инклюде.
Вторым встал вопрос о том, как реализовать параметризованные тесты. boost умеет параметризовать тесты, но выглядит это не очень изящно. Мне же хотелось увидеть минимум букв, что-то типа того:
Но реальность жестока... компилятор не может типизировать braces list во что-то конкретное, кроме std::initializer_list, но std::initializer_list должен состоять из элементов одного типа. Я должен явно сказать, что список состоит из std::tuple. И исходный список параметров трансформировался в:
Но и дальше не все так просто... для разворачивания макро UP_PARAMETRIZED_TEST мне необходимо явно прописать из чего состоит этот тупл.
Хотя возможно я погорячился и смогу убрать это чуть позже, может быть шаблоны прокатят. Но были какие-то причины и сложности. Tuple сохраняет строку как указатель на char, она не хочет сама по себе превращаться в string... пичаль. Внутри теста параметры раcтупливаются обычным способом. И их может быть произвольное количество.
По моему не плохо получилось...
Фикстуры реализуются достаточно просто, поэтому я и параметризованные тесты дополнил фикстурами, если необходимо.
Пришлось изрядно повозиться с универсальным сравнителем. Очень раздражает BOOST_REQUIRE_COLLECTIONS_EQUAL. У меня все сравнивается через UP_ASSERT_EQUAL (ну или не сравнивается через UP_ASSERT_NE). В него можно передать числа, строки, коллекции разных типов. Коллекции сравниваются по содержимому. Если что-то не сравнивается - то ошибки компиляции не будет - при запуске сфейлится утверждение и напечатает всю ту шнягу, которую в него запихнули.
Интересная история с ASSERT_EXCEPTION. Этот метод я посчитал необходимым, поскольку часто им пользуюсь. Но передавать в макро код, который должен выполниться в рамках try/catch - cчитаю крайне неестественным. Если мы хотим передать куда-то код - мы должны описать блок кода, как будто наше макро - часть языка. Или на худой конец передать сallable object, чтобы было похоже что наше макро - функция.
Первый вариант на первый взгляд показался совершенно нереализуемым:
Но мне кажется, что такое макро можно написать. Просто сразу что-то не заработало и я решил не заморачиваться. К тому же мучают сомнения насчет того, какой вариант более естественно выглядит для плюсов. Остановился на варианте с лямбдами.
Тесты можно группировать в наборы, но выборочное выполнение пока не сделано.
И чтобы вообще не заморачиваться - в одном из тестовых модулей нужно написать:
этот стандартный main поддерживает тихий запуск теста, тайминг по тестам, определение порядка выполнения тестов (через рандом сид) и все. Ничего лишнего.
Не стал делать сравнение с плавающей точкой. Во первых сам почти не работаю с плавающей точкой. Во вторых моя универсальная функция сравнения принимает только два параметра, описывать отдельную - помоему не очень хорошая идея. Любое, самое извращенное сравнение можно реализовать через UP_ASSERT. :)
На этом пока все. Follow me on GitHub.
Закончилось все тем, что решил написать свой фреймворк, с
Первый вопрос - можно ли реализовать синглтон в инклюде. Это необходимо во фреймворке, для того, чтобы тесты из всех исходников регистрировались в одной коллекции. А создавать фреймворк в виде библиотеки ну очень не хотелось.
Синглтон от GoF требует .сpp файл для описания статического указателя - не вариант. А вот синглтон Майерса - прекрасно живет в инклюде.
class TestCollection { static TestCollection &getInstance() { static TestCollection collection; return collection; };
Вторым встал вопрос о том, как реализовать параметризованные тесты. boost умеет параметризовать тесты, но выглядит это не очень изящно. Мне же хотелось увидеть минимум букв, что-то типа того:
const auto params = { { 1, "one" }, { 2, "two" } }; UP_PARAMETRIZED_TEST(test, params) { ... }
Но реальность жестока... компилятор не может типизировать braces list во что-то конкретное, кроме std::initializer_list, но std::initializer_list должен состоять из элементов одного типа. Я должен явно сказать, что список состоит из std::tuple. И исходный список параметров трансформировался в:
const auto params = { make_tuple(1, "one"), make_tuple(2, "two") };
Но и дальше не все так просто... для разворачивания макро UP_PARAMETRIZED_TEST мне необходимо явно прописать из чего состоит этот тупл.
UP_PARAMETRIZED_TEST(test, params, int, const char *) { ... }
Хотя возможно я погорячился и смогу убрать это чуть позже, может быть шаблоны прокатят. Но были какие-то причины и сложности. Tuple сохраняет строку как указатель на char, она не хочет сама по себе превращаться в string... пичаль. Внутри теста параметры раcтупливаются обычным способом. И их может быть произвольное количество.
static const auto encrypt_params = { make_tuple(key01, 0xCCCCCCCCU, 0x33333333U, 0xF5FE5211U, 0x17E8D02EU), make_tuple(key01, 0x33333333U, 0xCCCCCCCCU, 0x6390ED97U, 0x3A962C89U), make_tuple(key02, 0xCCCCCCCCU, 0x33333333U, 0x2A78B7E0U, 0x800A0268U), make_tuple(key02, 0x33333333U, 0xCCCCCCCCU, 0x462DA336U, 0xEAB90129U), make_tuple(key03, 0xCCCCCCCCU, 0x33333333U, 0x8BB8CF97U, 0x533CDA6BU), make_tuple(key03, 0x33333333U, 0xCCCCCCCCU, 0xBE407AB5U, 0x5C055B4FU), make_tuple(key04, 0xCCCCCCCCU, 0x33333333U, 0x895A9742U, 0x02DB134CU), make_tuple(key04, 0x33333333U, 0xCCCCCCCCU, 0xDAA70325U, 0xB95DDC39U), make_tuple(key05, 0xCCCCCCCCU, 0x33333333U, 0x401EBED9U, 0x56F5D77DU), make_tuple(key05, 0x33333333U, 0xCCCCCCCCU, 0x4E790503U, 0x73FE0118U), }; UP_PARAMETRIZED_TEST(encryptShouldBeCorrect, encrypt_params, vector<uint8_t>, uint32_t, uint32_t, uint32_t, uint32_t) { const auto key = get<0>(encrypt_params); const auto A = get<1>(encrypt_params); const auto B = get<2>(encrypt_params); const auto eA = get<3>(encrypt_params); const auto eB = get<4>(encrypt_params);
По моему не плохо получилось...
Фикстуры реализуются достаточно просто, поэтому я и параметризованные тесты дополнил фикстурами, если необходимо.
Пришлось изрядно повозиться с универсальным сравнителем. Очень раздражает BOOST_REQUIRE_COLLECTIONS_EQUAL. У меня все сравнивается через UP_ASSERT_EQUAL (ну или не сравнивается через UP_ASSERT_NE). В него можно передать числа, строки, коллекции разных типов. Коллекции сравниваются по содержимому. Если что-то не сравнивается - то ошибки компиляции не будет - при запуске сфейлится утверждение и напечатает всю ту шнягу, которую в него запихнули.
Интересная история с ASSERT_EXCEPTION. Этот метод я посчитал необходимым, поскольку часто им пользуюсь. Но передавать в макро код, который должен выполниться в рамках try/catch - cчитаю крайне неестественным. Если мы хотим передать куда-то код - мы должны описать блок кода, как будто наше макро - часть языка. Или на худой конец передать сallable object, чтобы было похоже что наше макро - функция.
Первый вариант на первый взгляд показался совершенно нереализуемым:
UP_ASSERT_EXCEPTION(std::exception) { ... };
Но мне кажется, что такое макро можно написать. Просто сразу что-то не заработало и я решил не заморачиваться. К тому же мучают сомнения насчет того, какой вариант более естественно выглядит для плюсов. Остановился на варианте с лямбдами.
UP_ASSERT_EXCEPTION(std::exception, []{ ... }); UP_ASSERT_EXCEPTION(std::exception, message, []{ ... });
Тесты можно группировать в наборы, но выборочное выполнение пока не сделано.
И чтобы вообще не заморачиваться - в одном из тестовых модулей нужно написать:
UP_MAIN();
этот стандартный main поддерживает тихий запуск теста, тайминг по тестам, определение порядка выполнения тестов (через рандом сид) и все. Ничего лишнего.
Не стал делать сравнение с плавающей точкой. Во первых сам почти не работаю с плавающей точкой. Во вторых моя универсальная функция сравнения принимает только два параметра, описывать отдельную - помоему не очень хорошая идея. Любое, самое извращенное сравнение можно реализовать через UP_ASSERT. :)
На этом пока все. Follow me on GitHub.
0 коммент.:
Отправить комментарий