Мені справді потрібна одиниця тестової рамки?


19

В даний час на моїй роботі у нас є великий набір одиничних тестів для нашої програми C ++. Однак ми не використовуємо одиничну тестову рамку. Вони просто використовують макрос C, який в основному завершує ствердження і cout. Щось на зразок:

VERIFY(cond) if (!(cond)) {std::cout << "unit test failed at " << __FILE__ << "," << __LINE__; asserst(false)}

Тоді ми просто створюємо функції для кожного нашого тесту

void CheckBehaviorYWhenXHappens()
{
    // a bunch of code to run the test
    //
    VERIFY(blah != blah2);
    // more VERIFY's as needed
}

Наш сервер CI підбирає "тест блоку не вдався" і не вдалося зібрати, надіславши повідомлення розробникам.

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

Я знаю, що там є такі рамки, як тест на CppUnit і тест підсилення. Мені цікаво, яку цінність вони додають? Я пропускаю те, що вони приносять до столу? Чи є щось корисне, що я міг би отримати від них? Я вагаюсь, як додати залежність, якщо це не додасть реальної цінності, тим більше, що здається, що у нас є мертвий простий і працює добре.

Відповіді:


8

Як вже говорили інші, у вас вже є своя, проста, домашня основа.

Здається, це банально зробити. Однак є деякі інші особливості одиничного тестування, які не так просто реалізувати, оскільки вони вимагають певних передових знань про мову. Особливості, які я, як правило, вимагаю від тестових рамок, і які не є легкими для домашнього перекладу:

  • Автоматичний збір тестових випадків. Тобто визначення нового методу тестування повинно бути достатньо для його виконання. JUnit автоматично збирає всі методи, імена яких починаються з test, NUnit має [Test]анотацію, Boost.Test використовує BOOST_AUTO_TEST_CASEі BOOST_FIXTURE_TEST_CASEмакроси.

    Це здебільшого зручність, але кожен зручність, яку ви можете отримати, покращує шанси розробників фактично написати тести, які вони повинні, і що вони підключать їх правильно. Якщо у вас є довгі інструкції, хтось пропустить частину з них зараз, і, можливо, деякі тести не будуть запущені, і ніхто не помітить.

  • Можливість запускати вибрані тестові випадки, без підстроювання коду та перекомпіляції. Будь-яка пристойна рамка тестування одиниць дозволяє вказати, які тести потрібно запустити в командному рядку. Якщо ви хочете налагоджувати тести на одиницях (це найважливіший момент у них для багатьох розробників), вам потрібно мати можливість вибрати лише деякі для запуску, не змінюючи код у всьому місці.

    Скажімо, ви щойно отримали звіт про помилку № 4211, і він може бути відтворений за допомогою тесту на одиницю. Отже, ви пишете один, але ніж вам потрібно сказати бігуну провести саме цей тест, щоб ви могли налагодити те, що насправді там не так.

  • Можливість відмітити очікувані збої в тестах за кожний тестовий випадок, не змінюючи самих чеків. Ми фактично переключили рамки на роботі, щоб отримати цю.

    Будь-який тестовий набір пристойного розміру матиме тести, які не спрацьовують, оскільки функції, які вони перевіряли, ще не були реалізовані, ще не закінчені, ніхто ще не встиг їх виправити чи щось. Без здатності відзначати тести як очікувані збої, ви не помітите черговий збій, коли вони є регулярно, тому тести перестають служити їх основній меті.


дякую, я думаю, це найкраща відповідь. Зараз мій макрос робить свою роботу, але я не можу виконувати жодну з функцій, які ви згадуєте.
Дуг Т.

1
@Jan Hudec "Це здебільшого зручність, але кожен зручність, який ви можете отримати, покращує шанси розробників фактично написати тести, які вони повинні, і що вони з’єднають їх правильно"; Усі рамки тестування (1) нетривітні для встановлення, часто мають більш застарілі чи неістотні інструкції щодо встановлення, ніж актуальні дійсні інструкції; (2) якщо ви скористаєтеся тестовою рамкою безпосередньо, без інтерфейсу посередині, ви одружені з нею, перемикати рамки не завжди просто.
Дмитро

@Jan Hudec Якщо ми очікуємо, що більше людей буде писати одиничні тести, ми повинні мати більше результатів на Google для "Що таке одиничне тестування", ніж "Що таке тестування одиниць". Немає сенсу проводити тестування одиниць, якщо ви не маєте уявлення про те, що тест одиниці залежить від будь-яких рамок одиничного тестування або визначення модульних тестувань. Ви не можете робити тестові одиниці, якщо ви чітко не розумієте, що таке одиничне тестування, оскільки в іншому випадку немає сенсу робити тестування.
Дмитро

Я не купую цей аргумент зручності. Писати тестовий код дуже важко, якщо залишити тривіальний світ прикладів. Все це макети, налаштування, бібліотеки, зовнішні програми сервера макетів тощо. Всі вони вимагають, щоб ви знали тестові рамки зсередини.
Лотар

@ Лотар, так, це все багато роботи і багато чому навчитися, але все-таки потрібно писати прості кофейні панелі знову і знову, тому що вам не вистачає пари корисних утиліт робить роботу набагато менш приємною і це робить помітну різницю в ефективності.
Ян Худек

27

Здається, ви вже використовуєте рамку, домашню.

Яка додаткова вартість більш популярних фреймворків? Я б сказав, що цінність, яку вони додають, полягає в тому, що коли вам доведеться обмінюватися кодом з людьми поза вашою компанією, ви можете це робити, оскільки це базується на тій рамці, яка відома і широко використовується .

З іншого боку, домашній фреймворк змушує вас ніколи не ділитися кодом, ані надавати сам фреймворк, який може стати громіздким із ростом самого фреймворку.

Якщо ви надасте свій колега такому, який є, без пояснень та опрацьованих рамок тестування, він не зможе його скласти.

Другий недолік домашніх каркасів - сумісність . Популярні тестові рамки пристрою, як правило, забезпечують сумісність з різними IDE, системами управління версіями тощо. На даний момент це може не бути для вас дуже важливим, але що буде, якщо одного дня вам потрібно буде щось змінити на сервері CI або перемістити до нового IDE чи нового VCS? Будете винаходити колесо?

І останнє, але не менш важливе, більші рамки надають більше можливостей, які вам можуть знадобитися впровадити у власні рамки одного дня. Assert.AreEqual(expected, actual)не завжди достатньо. Що робити, якщо вам потрібно:

  • вимірювати точність?

    Assert.AreEqual(3.1415926535897932384626433832795, actual, 25)
    
  • нікчемний тест, якщо він працює занадто довго? Повторне виконання тайм-ауту не може бути простим навіть у мовах, що полегшують асинхронне програмування.

  • перевірити метод, який очікує, що буде виключено виняток?

  • маєте більш елегантний код?

    Assert.Verify(a == null);
    

    це добре, але чи не виразніше ваш намір написати наступний рядок замість цього?

    Assert.IsNull(a);
    

"Рамка", яку ми використовуємо, міститься у дуже маленькому файлі заголовка і відповідає семантиці затвердження. Тому я не надто переживаю недоліки, які ви перераховуєте.
Дуг Т.

4
Я вважаю твердження найбільш тривіальною частиною тестових рамок. Бігун, який збирає та запускає тестові приклади та перевіряє результати, є нетривіальною важливою частиною.
Ян Худек,

@Jan Я не дуже дотримуюся. Мій бігун - це головний розпорядок, спільний для кожної програми на C ++. Чи робить тестувальник рамки тестування щось більш складне і корисне?
Дуг Т.

1
Ваша структура дозволяє лише семантику затвердження та запуску тестів в основному методі ... поки що. Просто зачекайте, поки вам доведеться згрупувати свої твердження за декількома сценаріями, спільними сценаріями, пов'язаними з групою, на основі ініціалізованих даних тощо.
Джеймс Кінгсбері

@DougT. Так, гідний бігунок тестової одиниці тестує ще кілька складних корисних речей. Дивіться мою повну відповідь.
Ян Худек

4

Як уже говорили інші, у вас вже є своя, зроблена в домашніх умовах рамка.

Єдиною причиною, яку я бачу для використання деяких інших тестових рамок, було б з галузевої точки зору "загальновідомі". Новим розробникам не доведеться вивчати домашній спосіб (хоча це виглядає дуже просто).

Також інші тестові рамки можуть мати більше функцій, якими ви могли б скористатися.


1
Домовились. Якщо ви не стикаєтесь з обмеженнями у своїй поточній стратегії тестування, я бачу мало підстав для зміни. Хороша структура, ймовірно, забезпечить кращі можливості організації та звітності, але вам доведеться обґрунтувати додаткову роботу, необхідну для інтеграції з вашою кодовою базою (включаючи систему складання).
TMN

3

У вас вже є рамка, навіть якщо вона є простою.

Основними перевагами більшого фреймворку, як я їх бачу, є можливість мати багато різних типів тверджень (таких як ствердження підвищення), логічне впорядкування одиничних тестів та можливість запускати лише підмножину одиничних тестів на Час. Крім того, ви зможете дотримуватися шаблону тестів xUnit, якщо зможете - наприклад, setUP () та tearDown (). Звичайно, це замикає вас у зазначених рамках. Зауважте, що деякі рамки мають кращу макетну інтеграцію, ніж інші - google макет та тест, наприклад.

Скільки часу вам знадобиться, щоб перевстановити всі ваші тести на новий модуль? Дні чи кілька тижнів, можливо, того варті, але більше, можливо, не так багато.


2

Як я це бачу, ви обидва маєте перевагу, і ви перебуваєте у "невигідності" (sic).

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

Однією з переваг добре розробленого API тестування модулів є те, що в більшості сучасних IDE існує велика підтримка. Це не вплине на жорстоких користувачів VI та emacs, які знущаються над користувачами Visual Studio там, але для тих, хто використовує хороший IDE, ви маєте можливість налагоджувати свої тести та виконувати їх у межах сама IDE. Це добре, однак є ще більша перевага залежно від використовуваної рамки, яка є в мові використовується для тестування вашого коду.

Коли я розмовляю мовою , я не говорю про мову програмування, а натомість я кажу про багатий набір слів, загорнутий у вільний синтаксис, який робить тестовий код прочитаним як історію. Зокрема, я став прихильником використання рамок BDD . Мій особистий улюблений DotNet BDD API - це StoryQ, але є кілька інших з тією ж основною метою, яка полягає в тому, щоб вийняти концепцію з документа про вимоги і записати її в код аналогічно тому, як це написано в специфікації. Однак дійсно хороші API-файли йдуть ще далі, перехоплюючи кожне окреме висловлювання в рамках тесту та вказуючи, успішно виконано, чи не вдалося. Це неймовірно корисно, оскільки ви бачите весь тест, виконаний без повернення рано, а це означає, що ваші зусилля налагодження стають неймовірно ефективними, оскільки вам потрібно лише зосередити свою увагу на частинах тесту, які не вдалися, не потребуючи розшифровувати весь виклик послідовність. Інша приємна річ - тест-результат показує вам всю цю інформацію,

Як приклад того, про що я говорю, порівняйте наступне:

Використання тверджень:

Assert(variable_A == expected_value_1); // if this fails...
Assert(variable_B == expected_value_2); // ...this will not execute
Assert(variable_C == expected_value_3); // ...and nor will this!

Використання вільного BDD API: (Уявіть, що курсивні біти - це в основному покажчики методу)

WithScenario("Test Scenario")
    .Given(*AConfiguration*) // each method
    .When(*MyMethodToTestIsCalledWith*, variable_A, variable_B, variable_C) // in the
    .Then(*ExpectVariableAEquals*, expected_value_1) // Scenario will
        .And(*ExpectVariableBEquals*, expected_value_2) // indicate if it has
        .And(*ExpectVariableCEquals*, expected_value_3) // passed or failed execution.
    .Execute();

Тепер наданий синтаксис BDD довший і більш важкий, і ці приклади страшенно надумані, однак для дуже складних ситуацій тестування, коли багато чого змінюється в системі внаслідок заданої поведінки системи, синтаксис BDD пропонує вам зрозумілий опис того, що ви протестуєте, і як була визначена ваша тестова конфігурація, і ви можете показати цей код непрограмісту, і він миттєво зрозуміє, що відбувається. Крім того, якщо "varia__A" провалить тест в обох випадках, приклад Asserts не виконає минуле першого затвердження, поки ви не усунете проблему, тоді як API BDD буде виконувати кожен метод, викликаний у ланцюзі, в свою чергу, і вказувати, який окремі частини твердження були помилковими.

Особисто я вважаю, що цей підхід працює набагато краще, ніж традиційні рамки xUnit, в тому сенсі, що мова тестування є тією ж мовою, що і ваші клієнти будуть говорити про свої логічні вимоги. Незважаючи на це, мені вдалося використовувати рамки xUnit у подібному стилі, не потребуючи винаходити повний API тестування, щоб підтримати мої зусилля, і, хоча твердження все одно будуть ефективно коротко замикатися, вони читають більш чисто. Наприклад:

Використання Nunit :

[Test]
void TestMyMethod()
{
    const int theExpectedValue = someValue;

    GivenASetupToTestMyMethod();

    var theActualValue = WhenIExecuteMyMethodToTest();

    Assert.That(theActualValue, Is.EqualTo(theExpectedValue)); // nice, but it's not BDD
}

Якщо ви все-таки вирішите дослідити, використовуючи API тестування модулів, моя порада - експериментувати з великою кількістю різних API протягом певного часу, а також зберігати та відкривати свою думку про свій підхід. Хоча я особисто виступаю за BDD, ваші власні потреби в бізнесі можуть вимагати чогось іншого для обставин вашої команди. Основне, однак, полягає у тому, щоб уникнути другого відгадування вашої існуючої системи. Ви завжди можете підтримувати наявні тести кількома тестами, використовуючи інший API, якщо це потрібно, але я, звичайно, не рекомендував би переписати величезний тест просто для того, щоб все було те саме. Оскільки застарілий код виходить з ужитку, ви можете легко замінити його та його тести на новий код та тести з використанням альтернативного API, і це не потребує великих вкладень у зусилля, які не обов'язково принесуть вам реальну цінність для бізнесу. Що стосується використання API тестування модулів,


1

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

Інша перевага використання основної системи - і це незначна користь, IMO - полягає в тому, що нові розробники вже можуть працювати на швидкості в будь-якій структурі, яку ви використовуєте, і тому буде потрібно менше навчання. На практиці, при прямолінійному підході, як і те, що ви описали, це не повинно бути великою справою.

Крім того, більшість основних фреймворків мають певні особливості, які може мати, а може і не мати. Ці функції зменшують код сантехніки та спрощують писати тестові випадки:

  • Автоматичне виконання тестових випадків, використовуючи умовні позначення, анотації / атрибути тощо.
  • Різні більш конкретні твердження, так що вам не потрібно писати умовної логіки для всіх ваших тверджень або виловлювати винятки, щоб стверджувати їх тип.
  • Категоризація тестових випадків, щоб ви могли легко запускати їх підмножини.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.