Чи є причина, що тести не записуються в рядку з кодом, який вони перевіряють?


91

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

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


33
Деякі мови програмування, такі як python з doctest, дозволяють це зробити.
Саймон Бергот

2
Ви можете відчути, що специфікації стилю BDD кращі за прозові при поясненні коду, але це не означає, що поєднання двох не є кращим.
JeffO

5
Половина аргументів тут стосується і вбудованої документації.
CodesInChaos

3
Документи @Simon занадто спрощені для серйозного тестування, здебільшого тому, що вони не призначені для цього. Вони були призначені для роботи та мають найкращі результати, маючи приклади коду в документації, яку можна перевірити автоматично. Зараз дехто використовує їх і для тестування одиниць, але останнім часом (як і в минулі роки) це потребувало великих помилок, оскільки воно, як правило, закінчується крихкими помилками, надмірно багатослівною «документацією» та іншими помилками.

7
Дизайн за контрактом дозволяє вбудовувати технічні характеристики, які роблять тестування простим.
Фурманатор

Відповіді:


89

Єдиною перевагою, яку я можу придумати для вбудованих тестів, буде зменшення кількості записуваних файлів. З сучасними ІДЕ насправді це не так вже й багато.

Однак існує ряд очевидних недоліків для вбудованого тестування:

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

9
Це цікаво. Я здогадуюсь, що перевага, яку я бачу, полягає в тому, що, коли ви наділи шапку «кодер», ви хочете подумати про тести, але це добре, що зворотне не відповідає дійсності.
Кріс Девере

2
У зв'язку з цим можна (і, можливо, бажано), щоб одна людина створювала тести, а друга фактично реалізувала код. Встановлення тестів в оренду робить це складніше.
Jim Nutt

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

5
@bharal Крім того, wrt до "Просто занадто важко", мазохізм - це чеснота дурня. Я хочу, щоб все було легко, крім проблеми, яку я насправді намагаюся вирішити.
deworde

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

36

Я можу подумати про деякі:

  • Читабельність. Інтерперсинг "реального" коду та тестів ускладнить читання реального коду.

  • Код роздуття. Змішування "реального" коду та тестового коду в одних і тих же файлах / класах / що завгодно може призвести до більшого розміру файлів і т. Д. Це особливо важливо для мов з пізнім прив'язкою.

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

Зараз можливі шляхи вирішення кожного з цих питань. Але ІМО, простіше не їхати туди в першу чергу.


Варто зауважити, що в перші дні програмісти Java раніше робили подібні речі; наприклад, включення main(...)методу в клас для полегшення тестування. Ця ідея майже повністю зникла. Це галузева практика застосовувати тести окремо, використовуючи якусь тестову рамку.

Варто також зауважити, що грамотне програмування (за задумом Кнута) ніколи не натрапляло на галузь інженерії програмного забезпечення.


4
+1 Проблеми з читабельністю - тестовий код може бути пропорційно більшим коду реалізації, особливо в проектах ОО.
Фурманатор

2
+1 для вказівки за допомогою тестових рамок. Я не можу уявити собі використання хорошої тестової основи одночасно з виробничим кодом.
joshin4colours

1
RE: Ви, можливо, не хочете, щоб ваші клієнти / клієнти бачили ваш тестовий код. (Мені це не подобається ... але якщо ви працюєте над проектом із закритим джерелом, тестовий код навряд чи допоможе замовнику.) - Можливо, бажано запустити тести на машині клієнтів. Запуск тестів може допомогти швидко визначити, у чому проблема, та визначити відмінності в
оточенні

1
@sixtyfootersdude - це досить незвична ситуація. І якщо припустити, що ви розробляли закрите джерело, ви не хочете включати свої тести у стандартний бінарний дистрибутив на всякий випадок. (Ви створили б окремий пакет, що містить тести, які потрібно запустити клієнту.)
Stephen C

1
1) Ви пропустили першу частину моєї відповіді, де я дав три фактичні причини? Там була задіяна якась "критична думка" .... 2) Ви пропустили другу частину, де я казав, що раніше це робили програмісти Java, але зараз це не робити? І очевидний сенс, що програмісти перестали це робити ... з поважних причин?
Стівен С

14

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

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


13

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

Створення: Тести та код можуть бути написані різними людьми.

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

Повторність використання: Якщо ви поставите тести вбудовані, ви не можете використовувати їх з іншим фрагментом коду.

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

Вибірність: Тримання тестів окремо від коду полегшує вибір тестів, які потрібно запустити.

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


Мене спантеличують ваші причини: TDD вже говорить, що створення тесту відбувається раніше (або одночасно) з виробничим кодом, і його повинен робити той самий кодер! Вони також натякають, що тести майже схожі на вимоги. Звичайно, ці заперечення не застосовуються, якщо ви не підписуєтесь на догматику TDD (що було б прийнятно, але ви повинні це зрозуміти!). Також, що саме є "багаторазовим" тестом? Чи не тести, за визначенням, специфічні для коду, який вони перевіряють?
Андрес Ф.

1
@AndresF. Ні, тести не характерні для коду, який вони перевіряють; вони характерні для поведінки, на яку вони тестуються. Так, скажімо, у вас є модуль віджетів із набором тестів, які підтверджують, що віджет веде себе правильно. Ваш колега придумав BetterWidget, який планує зробити те саме, що і віджет, але втричі швидше. Якщо тести на Widget вбудовані у вихідний код віджета так само, як Literate Programming вбудовує документацію у вихідний код, ви не можете дуже добре застосувати ці тести до BetterWidget, щоб переконатися, що він поводиться так само, як це робить Widget.
Калеб

@AndresF. не потрібно вказувати, що ви не дотримуєтеся TDD. це не космічний дефолт. Щодо точки повторного використання. При тестуванні системи ви дбаєте про входи та виходи, а не про внутрішні. Коли вам тоді потрібно створити нову систему, яка веде себе так само, але реалізується інакше, чудово мати тести, які можна запускати як на старій, так і на новій системі. це траплялося зі мною не раз, іноді вам потрібно працювати над новою системою, поки стара ще продається або навіть запускати їх поруч. Подивіться на те, як Facebook випробовував "реагувати волокна" з тестами реагування, щоб отримати паритет.
користувач1852503

10

Ось кілька додаткових причин, про які я можу подумати:

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

  • тести функції, класу, бібліотеки, як правило, записуються з точки зору "користувачів" (користувач цієї функції / класу / бібліотеки). Такий "використання коду", як правило, записується в окремий файл або бібліотеку, і тест може бути чіткішим або "більш реалістичним", якщо він імітує цю ситуацію.


5

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


9
Не неможливо. Це вимагатиме додаткової фази попередньої обробки, як і LP. Наприклад, це можна зробити легко на C або в мові компіляції до js, наприклад.
Кріс Девере

+1, щоб вказати на мене. Я відредагував свою відповідь, щоб це представляти.
1313

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

5

Ця ідея просто є методом "Self_Test" в контексті об'єктно-орієнтованого дизайну. Якщо використовується компільована об'єктна мова на зразок Ada, весь код самотестування буде позначений компілятором як невикористаний (ніколи не викликаний) під час виробничої компіляції, і тому все це буде оптимізовано - жодне з них не з’явиться в отриманий виконуваний файл.

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

Об'єктно-орієнтовані мови часто надають саме такий тип дисципліни стосовно методів, що знаходяться поза тестованим об'єктом (вони застосовують специфікацію об'єкта, запобігаючи будь-якому доступу до деталей його реалізації та підвищуючи помилку компіляції, якщо будь-яка така спроба виявлена ). Але внутрішні методи об'єкта отримують повний доступ до кожної деталі реалізації. Таким чином, метод самотестування знаходиться в унікальній ситуації: він повинен бути внутрішнім методом через свою природу (самовипробування, очевидно, є методом тестуваного об'єкта), але він повинен отримати всю дисципліну компілятора зовнішнього методу ( він повинен бути незалежним від деталей реалізації об'єкта). Мало, якщо якісь мови програмування надають можливість дисциплінувати предмет " s внутрішній метод, як ніби це зовнішній метод. Отже, це важливе питання дизайну мови програмування.

За відсутності належної підтримки мови програмування найкращий спосіб зробити це - створити супутній об’єкт. Іншими словами, для кожного об'єкта, який ви кодуєте (назвемо його "Big_Object"), ви також створюєте другий супутній об'єкт, ім'я якого складається зі стандартного суфікса, об'єднаного з іменем "реального" об'єкта (у цьому випадку "Big_Object_Self_Test" "), специфікація якого складається з єдиного методу (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) повертає булевий; "). Потім об'єкт-супутник буде залежати від специфікації основного об'єкта, і компілятор буде повністю виконувати всю дисципліну цієї специфікації проти реалізації об'єкта-супутника.


4

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

У випадку c # (я також вважаю, що і c ++, синтаксис може дещо відрізнятися залежно від того, який компілятор ви використовуєте) ось так ви можете це зробити.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

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


2

Ми використовуємо вбудовані тести з нашим кодом Perl. Існує модуль Test :: Inline , який генерує тестові файли з вбудованого коду.

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

Відповідаючи на пару занепокоєнь:

  • Вбудовані тести записуються у розділи POD, тому вони не є частиною фактичного коду. Інтерпретатор їх ігнорує, тому немає коду.
  • Ми використовуємо складку Vim для приховування тестових секцій. Єдине, що ви бачите - це один рядок над кожним методом, який тестується +-- 33 lines: #test----. Коли ви хочете працювати з тестом, ви просто розширите його.
  • Модуль Test :: Inline "збирає" тести до звичайних TAP-сумісних файлів, щоб вони могли співіснувати з традиційними тестами.

Довідково:


1

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


1

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

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


0

Це неправда. Набагато краще розмістити свої одиничні тести поруч із виробничим кодом, коли виробничий код, особливо коли виробнича процедура є чистою.

Наприклад, якщо ви розробляєте під .NET, наприклад, ви можете помістити свій тестовий код у виробничу збірку, а потім скористатися Scalpel, щоб видалити їх перед відправкою.

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