Тестування одиниць C ++: Що перевірити?


20

TL; DR

Писати хороші, корисні тести важко і має високу вартість на C ++. Чи можете ви досвідчені розробники поділитися вашим обґрунтуванням того, що і коли тестувати?

Довга історія

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

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

Тепер я успадкував новий проект, і мені цікаво, як продовжувати тестувати його. Це рідний додаток C ++ / OpenGL, тому тести на інтеграцію насправді не є варіантом. Але тестування одиниць на C ++ трохи складніше, ніж у Java (ви повинні чітко робити речі virtual), і програма не сильно орієнтована на об’єкти, тому я не можу глузувати / заглушувати деякі речі.

Я не хочу розривати і OO-ize всю справу просто написати тести заради написання тестів. Тож я запитую вас: для чого я повинен писати тести? наприклад:

  • Функції / класи, які, як я думаю, часто змінюватиму?
  • Функції / класи, які складніше перевірити вручну?
  • Функції / класи, які вже легко перевірити?

Я почав досліджувати деякі шановні бази C ++, щоб побачити, як вони проходять тестування. Зараз я переглядаю вихідний код Chromium, але мені важко витягнути з цього коду обґрунтування тестування. Якщо хтось має хороший приклад або опублікує, як популярні користувачі C ++ (хлопці з комітету, автори книг, Google, Facebook, Microsoft, ...) підходять до цього, це було б корисно.

Оновлення

Я шукав свій шлях по цьому веб-сайту та Інтернету з часу написання цього. Знайшов кілька хороших речей:

На жаль, все це скоріше орієнтоване на Java / C #. Написання безлічі тестів на Java / C # не є великою проблемою, тому вигода зазвичай переважає витрати.

Але як я писав вище, у C ++ складніше. Тим більше, якщо ваша база коду не є такою, що вам належить, ви повинні сильно зіпсувати речі, щоб отримати хороше покриття тестових одиниць. Наприклад: У додатку, який я успадкував, є Graphicsпростір імен, який є тонким шаром над OpenGL. Для того щоб перевірити будь-яку сутність, яка всі безпосередньо використовує її функції, я повинен був би перетворити це на інтерфейс та клас та ввести його у всі сутності. Це лише один приклад.

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


3
+1 за труднощі в тестуванні одиниць C ++. Якщо ваш тест на пристрій вимагає від вас змінити код, не робіть цього.
DPD

2
@DPD: Я не дуже впевнений, що робити, якщо щось дійсно варто тестувати? У поточній базі коду я навряд чи зможу щось перевірити в симуляційному коді, оскільки все це викликає графічні функції безпосередньо, і я не можу їх знущати / заглушувати. Все, що я зараз можу перевірити, - це корисні функції. Але я згоден, змінивши код, щоб він "перевірявся" відчував себе ... неправильно. Прихильники TDD часто кажуть, що це полегшить ваш код усіма можливими способами, але я смиренно не згоден. Не все потребує інтерфейсу та декількох реалізацій.
futlib

Дозвольте навести останній приклад: я провів цілий день, намагаючись випробувати одну функцію сингера (написану на C ++ / CLI), і тестовий інструмент MS Test завжди вийшов з ладу для цього тесту. Здавалося, у певних проблем із CPP-посиланнями є проблеми. Натомість я просто перевірив вихідну функцію його виклику, і вона спрацювала нормально. Я витрачав цілий день на одну функцію. Це була втрата дорогоцінного часу. Крім того, я не міг отримати жодного засобу для заглушки, відповідного моїм потребам. Я робив ручну заглушку, де це було можливо.
DPD

Це якраз те, чого я хотів би уникати: DI гадаю, ми з C ++ розробниками повинні бути особливо прагматичними щодо тестування. Ви в кінцевому підсумку тестували його, тож я гадаю, що це нормально.
futlib

@DPD: Я поміркував дещо більше про це, і, думаю, ти маєш рацію, питання полягає в тому, який вид компромісу я хочу зробити. Чи варто переробляти всю графічну систему, щоб перевірити пару об'єктів? Там не було жодної помилки, я, мабуть, так: Ні. Якщо вона почне відчувати себе баггі, я напишу тести. Шкода, що я не можу прийняти вашу відповідь, тому що це коментар :)
futlib

Відповіді:


5

Ну, тестування блоків - це лише одна частина. Інтеграційні тести допоможуть вам у вирішенні проблеми вашої команди. Інтеграційні тести можна писати для всіх типів програм, також для рідних програм та OpenGL. Слід перевірити "Вирощування об'єктно-орієнтованого програмного забезпечення, керованого тестами" Стіва Фрімана та Ната Прайса (наприклад, http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Це веде вас крок за кроком через розробку програми з графічним інтерфейсом та мережевим зв’язком.

Тестування програмного забезпечення, яке не тестоване, - це вже інша історія. Перевірте Майкл Пір'я "Ефективна робота зі спадковим кодом" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).


Я знаю обидві книги. Речі такі: 1. Ми не хочемо їхати з TDD, оскільки це не спрацювало з нами. Ми хочемо тестів, але не релігійно. 2. Я впевнений, що тести інтеграції для програм OpenGL можливі якось, але це вимагає занадто великих зусиль. Я хочу продовжувати вдосконалювати додаток, а не запускати дослідницький проект.
futlib

Майте на увазі, що тестування одиниць "після факту" завжди складніше, ніж "тестувати спочатку", оскільки код не може бути перевіреним (повторним використанням, ремонтом тощо). Якщо ви хочете все-таки зробити це, спробуйте дотримуватися хитрощів Майкла Пір'я (наприклад, шви), навіть якщо ви уникаєте TDD. Наприклад, якщо ви хочете розширити функцію, спробуйте щось подібне до "паросткового методу" і спробуйте зберегти новий метод перевіряючим. Це можна зробити, але важче ІМХО.
EricSchaefer

Я погоджуюся, що не TDD-код не розроблений для перевірки, але я б не сказав, що він не піддається ремонту або повторному використанню - як я вже коментував вище, для деяких речей просто не потрібні інтерфейси та кілька реалізацій. Зовсім не проблема з Mockito, але в C ++ мені потрібно зробити всі функції, які я хочу заглушити / знущатися з віртуальних. У будь-якому разі, проблема, що не перевіряється, є моєю найбільшою проблемою зараз: я повинен змінити деякі дуже фундаментальні речі, щоб зробити деякі частини перевірятими, і тому я хочу добре обґрунтувати тестування, щоб переконатися, що воно того варте.
futlib

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

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

2

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

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

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

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


4
Ми робили TDD близько півтора року, всі пристрасно про це. Але порівнюючи проекти TDD з попередніми, виконані без TDD (але не без тестів), я б не сказав, що вони насправді більш стабільні або мають краще розроблений код. Можливо, це наша команда: ми багато збираємось та переглядаємо, якість нашого коду завжди була дуже хорошою.
futlib

1
Чим більше я думаю про це, тим більше я думаю, що TDD просто не дуже підходив до технології конкретного проекту: Flex / Swiz. Існує багато подій, пов'язок та ін'єкцій, які ускладнюють взаємодію між об'єктами та майже неможливо перевірити. Роз'єднання цих об'єктів не робить його кращим, оскільки вони працюють коректно.
futlib

2

Я ще не робив TDD в C ++, тому я не можу це коментувати, але ви повинні перевірити очікувану поведінку вашого коду. Хоча реалізація може змінюватися, поведінка повинна (як правило?) Залишатися такою ж. У Java \ C # центричному світі це означатиме, що ви протестуєте лише загальнодоступні методи, пишете тести на очікувану поведінку і робите це перед впровадженням (що, як правило, краще сказати, ніж зробити :)).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.