Інструменти open source для модульного тестування c

Арпан Сіна. технічний директор, Synapti Computer Aided Design Pvt Ltd

Арпан вересня (Arpan Sen) - провідний інженер, що працює над розробкою програмного забезпечення в області автоматизації електронного проектування. Протягом декількох років він працював над деякими функціями UNIX, в тому числі Solaris, SunOS, HP-UX та IRIX, а також Linux і Microsoft Windows. Він проявляє жвавий інтерес до методик оптимізації продуктивності програмного забезпечення, теорії графів і паралельних обчислень. Арпан є аспірантів в області програмних систем.

Що таке модульне тестування?

З великою часткою ймовірності складний код, написаний на мові C / C ++. містить помилки. Після того, як код написаний, знайти ці помилки буде також складно, як знайти голку в стозі сіна. Більш розумний підхід полягає в тестуванні окремих фрагментів коду в міру їх створення. Для цього створюються невеликі (модульні) тести для перевірки конкретних параметрів, наприклад, деякої функції на мові C. виконує інтенсивні обчислення, або деякого класу C ++. що вимагає створення певної структури даних, таких як черга. Комплекс регресійних тестів, створений відповідно до цієї філософією, буде включати в себе набір модульних тестів і керуючу програму, яка буде запускати ці тести і повідомляти про результати.

Створення тесту для певної функції або класу

Розглянемо простий строковий клас, наведений у лістингу 1. Цей клас не дуже надійний, тому ми протестуємо його за допомогою Boost.

Лістинг 1. Звичайний строковий клас

Ряд типових тестів перевіряє, чи має порожній рядок нульову довжину, чи не відбулося вихід за межі індексу, що може відображатися в повідомленнях про помилки або винятки, і т. Д. У лістингу 2 наведено деякі тести, які важливо виконувати для будь-якої строкової реалізації. Щоб виконати код лістингу 2. просто скомпілюйте його за допомогою g ++ (або будь-якого іншого компілятора, сумісного зі стандартами C ++). При цьому немає необхідності як в відокремленні головної функції, так і у використанні будь-яких бібліотек компонування: всі необхідні визначення включені в заголовок unit_test.hpp, який є частиною установки Boost.

Лістинг 2. Модульні тести для строкового класу

Макроси BOOST_AUTO_TEST_SUITE і BOOST_AUTO_TEST_SUITE_END визначають початок і кінець тестового пакета, відповідно. Окремі тести розташовуються між цими макросами, і в цьому плані їх семантика подібна просторів імен C ++. Кожен окремий модульний тест визначається за допомогою макросу BOOST_AUTO_TEST_CASE. У лістингу 3 показані результати виконання коду лістингу 2.

Лістинг 3. Результати виконання коду лістингу 2

Давайте докладніше розглянемо процес створення модульних тестів з попередніх лістингів. Основна ідея полягає в тестуванні окремих властивостей класу за допомогою макросів Boost. Макроси BOOST_CHECK і BOOST_REQUIRE_EQUAL є зумовленими макросами (або інструментами тестування), включеними до складу фреймворка Boost, і призначені для перевірки правильності вихідних результатів коду.

Інструменти тестування Boost

Лістинг 4. Три варіанти використання інструментів тестування Boost

Перша перевірка BOOST_CHECK завершилася невдачею, так само як і перша перевірка BOOST_REQUIRE. Однак до другої перевірки BOOST_CHECK справа не дійшла, оскільки робота тесту була перервана відразу ж після невдалого завершення перевірки BOOST_REQUIRE. У лістингу 5 показані результати виконання коду лістингу 4.

Якщо для вивчення проблеми необхідно виконати перевірку окремих функцій або методів класу, найпростіше створити новий тест і викликати процедуру, передавши їй на вхід аргументи і очікувані значення. Такий приклад приведений в лістингу 6.

Лістинг 6. Використання Boost для перевірки функцій і методів класу

зіставлення зразків

Звичайною справою є порівняння отриманих результатів, що повертаються якою-небудь функцією, з еталонним значенням. Для таких випадків також дуже добре підходить макрос BOOST_CHECK. Крім цього необхідно використовувати клас output_test_stream зі складу бібліотеки Boost. Клас output_test_stream инициализируется за допомогою еталонного файлу (в наступному прикладі це файл run.log). Висновок функції C / C ++ передається в об'єкт output_test_stream. а потім викликається процедура match_pattern. Це докладно розглянуто в лістингу 7.

Лістинг 7. Зіставлення зразків за допомогою еталонного файлу

Порівняння величин з плаваючою комою

Однією з найскладніших перевірок в регресійному тестуванні є порівняння величин з плаваючою комою. Погляньте на код лістингу 8 - на перший погляд здається, що все працює нормально.

Лістинг 8. Порівняння величин з плаваючою комою, яке не працює

В процесі цього тесту виконання макросу BOOST_CHECK завершується з помилкою, незважаючи на використання функції sqrt. що входить до складу стандартної бібліотеки. У чому ж справа? Проблема порівняння величин з плаваючою комою полягає в точності: значення змінних f1 і result * result починають різнитися, починаючи з деякого знака після коми. Для виправлення цієї ситуації до складу інструментів тестування Boost входить макроси BOOST_WARN_CLOSE_FRACTION. BOOST_CHECK_CLOSE_FRACTION і BOOST_REQUIRE_CLOSE_FRACTION. Щоб використовувати будь-який з них, необхідно підключити зумовлений заголовок Boost floating_point_comparison.hpp. Всі три цих макросу мають однакову синтаксис тому розглянемо тільки варіант check (лістинг 9).

Лістинг 9. Синтаксис макросу BOOST_CHECK_CLOSE_FRACTION

Замість використання макросу BOOST_CHECK. як в лістингу 9. спробуйте використовувати макрос BOOST_CHECK_CLOSE_FRACTION з допустимою точністю 0,0001. Цей код приведений в лістингу 10.

Лістинг 10. Порівняння величин з плаваючою комою, яке працює

Цей код працює прекрасно. Тепер змінимо в лістингу 10 допустиму точність на 0,0000001. Результат показаний в лістингу 11.

Лістинг 11. Помилка при порівнянні через неприпустимий межі точності

Інша поширена (і досить складна) проблема, постійно зустрічається в робочих версіях додатків, полягає в порівнянні змінних типів double і float. Макрос BOOST_CHECK_CLOSE_FRACTION володіє чудовою особливістю, яка не дозволяє виконувати такі порівняння. Ліве і праве значення макросу повинні бути одного типу: float або double. Якщо в лістингу 12 змінна f1 матиме тип double, а результат result - тип float, під час компіляції відбудеться помилка.

Лістинг 12. Помилка: лівий і правий аргументи BOOST_CHECK_CLOSE_FRACTION мають різні типи

Підтримка користувальницьких предикатів

Інструменти тестування Boost можуть працювати з булевими умовами. Можна розширити ці інструменти так, щоб вони підтримували більш складні перевірки, наприклад визначення ідентичності вмісту двох списків чи перевірка виконання певної умови для всіх елементів вектора. Також можна розширити макрос BOOST_CHECK. забезпечивши підтримку користувальницьких предикатів. Спробуємо виконати спеціальну перевірку вмісту списку, згенерованого певної користувачем функцією мови C. і з'ясуємо, чи є значення всіх результуючих елементів більше 1. Функція користувальницької перевірки повинна повертати тип boost :: test_tools :: predicate_result. Детально це розглянуто в лістингу 13.

Лістинг 13. Перевірка складних предикатів за допомогою інструментів Boost

Об'єкт predicate_result має неявний конструктор, який приймає логічне значення. Це пояснює, чому код добре працює навіть в тих випадках, коли очікуваний і фактичний повертаються типи validate_list розрізняються.

Ще одним способом перевірки складних предикатів за допомогою Boost є використання макросу BOOST_CHECK_PREDICATE. Перевагою цього макросу є те, що він не використовує predicate_result. Недоліком його є деяка складність синтаксису. У макрос BOOST_CHECK_PREDICATE необхідно передавати ім'я функції і аргумент (або аргументи). Лістинг 14 робить те ж саме, що і лістинг 13. просто в ньому використовуються інші макроси. Зверніть увагу на те, що повертається тип validate_result тепер Boolean.

Лістинг 14. Макрос BOOST_CHECK_PREDICATE

Розміщення декількох тестових пакетів в одному файлі

Можна помістити кілька тестових пакетів в один файл. Кожен тестовий пакет повинен мати пару макросів BOOST_AUTO_TEST_SUITE. BOOST_AUTO_TEST_SUITE_END. визначених всередині файлу. У лістингу 15 показані два різних тестових пакета, поміщені в один файл. При виконанні регрессий запустіть виконуваний файл за допомогою визначеної опції -log_level = test_suite. Як видно з лістингу 16. висновок, що згенерував з використанням цієї опції, виявляється більш докладним і піддається швидкої налагодженні.

Лістинг 15. Кілька тестових пакетів, поміщених в один файл

Нижче наведено висновок, отриманий в результаті виконання коду лістингу 15.

Лістинг 16. Запуск декількох тестових пакетів з опцією -log_level

Розуміння структури тестового пакета

До сих пір ми обговорювали тестові пакети Boost, що не мають ієрархічної структури. Тепер спробуємо створити за допомогою Boost тестовий пакет, який тестує програмний продукт так, як зазвичай це бачить користувач зовнішніх інструментів. Всередині самого тестового фреймворка зазвичай присутні кілька тестових пакетів, кожен з яких перевіряє певні функції. Наприклад, фреймворк для регресійного тестування процесора Word повинен містити тестові пакети для перевірки підтримки шрифтів, різних файлових форматів і т. Д. Кожен окремий тестовий пакет містить кілька модульних тестів. У лістингу 17 наведено приклад тестового фреймворка. Зверніть увагу на те, що точкою входу, з якої починається виконання коду, повинна бути процедура, названа (належним чином) init_unit_test_suite.

Лістинг 17. Створення головного тестового пакета для виконання регрессий

Кожен тестовий пакет (наприклад, пакет ts1 з лістингу 17) створюється за допомогою макросу BOOST_TEST_SUITE. Макрос очікує рядок, що є ім'ям тестового пакета. Всі тестові пакети, в кінцевому рахунку, виявляються доданими в головний тестовий пакет за допомогою методу add. Подібним чином ви створюєте кожен тест за допомогою макросу BOOST_TEST_CASE і додаєте його в тестовий пакет за допомогою методу add. Також можна додати модульні тести в головний тестовий пакет, хоча робити цього не рекомендується. Метод master_test_suite визначено як частина простору імен boost :: unit_test :: framework - всередині він реалізує одноелементні безліч. Наведений у лістингу 18 код (міститься в исходниках самого Boost) пояснює, як це працює.

Лістинг 18. Розуміння методу master_test_suite

Тестові модулі, створені за допомогою макросу BOOST_TEST_CASE. приймають в якості вхідних аргументів покажчики на функції. Тому в лістингу 17 функції test_case1. test_case2 і т. д. є порожніми void-функціями, які користувач може запрограмувати так, як він захоче. Однак зверніть увагу на те, що тестова настройка Boost використовує певний невеликий обсяг пам'яті в купі; кожен виклик BOOST_TEST_SUITE зводиться до нового виклику boost :: unit_test :: test_suite (<имя тестового пакета>).

Тестова фікстура - це заздалегідь налаштоване стан середовища, в якій виконується тест. По завершенні виконання тесту всі налаштування цього середовища приймають свої первинні значення. Приклад фікстура показаний в лістингу 19.

Лістинг 19. Проста фікстура Boost

Результати показані в лістингу 20.

Лістинг 20. Результати використання фікстура Boost

Замість використання макросу BOOST_AUTO_TEST_CASE в цьому коді використовується макрос BOOST_FIXTURE_TEST_CASE. в який передається додатковий аргумент. Методи конструктора і деструктора цього об'єкта виконують необхідну настройку та очистку. Це підтверджує побіжний погляд на заголовок модуля Boost unit_test_suite.hpp (лістинг 21).

Лістинг 21. Визначення фікстура Boost в заголовку unit_test_suite.hpp

Boost публічно успадковує клас зі структури struct F (див. Лістинг 19) і створює на його основі об'єкт. Відповідно до правил публічного наслідування C ++ всі захищені і публічні змінні класу struct безпосередньо доступні з подальшою функції. Зверніть увагу на те, що змінна в лістингу 19 змінна i належить внутрішньому об'єкту t з типом F (див. Лістинг 20). Цілком нормально, якщо в тестовому пакеті регресійного тестування явна ініціалізація (отже, необхідність використання фікстур) потрібно лише для двох-трьох тестів. У лістингу 22 наведено тестовий пакет, в якому тільки один з трьох тестів використовує фікстур.

Лістинг 22. Тестовий пакет Boost, що містить тести з фікстур і без фікстур

У лістингу 22 містяться фікстура, певні і використовуються тільки в одному тесті. За допомогою макросу BOOST_GLOBAL_FIXTURE (<имя фикстуры>) Boost також дозволяє користувачам визначати і використовувати глобальні фікстура. Можна визначити будь-яку кількість глобальних фікстур, що дозволяють розділяти код ініціалізації. Приклад використання глобальної фікстура приведений в лістингу 23.

Лістинг 23. Використання глобальних фікстур для ініціалізації регресії

У разі декількох фікстур їх настройка і знищення відбуваються в тому порядку, в якому їх було оголошено. У лістингу 24 конструктор і деструктор класу F викликаються перш конструктора і деструктора класу F2 відповідно.

Лістинг 24. Використання декількох глобальних фікстур в регресії

Зверніть увагу на те, що не можна використовувати глобальні фікстура як об'єкти окремих тестів. Неможливо також отримати прямий доступ до їх публічним / захищеним нестатичних методам або змінним всередині тесту.

висновок

  • Оригінал статті Open source C / C ++ unit testing tools, Part 1: Get to know the Boost unit test framework (EN).
  • Документація Boost (EN) - вичерпне керівництво, присвячене інструментів тестування Boost
  • Політики і протоколи Boost (EN) - керівництво, присвячене політикам і протоколам тестування Boost
  • Список розсилки Boost (EN) - різна інформація, включаючи інструкції щодо усунення проблем під час установки і тестування

Схожі статті