Як я перевіряю систему, де над об'єктами важко знущатися?


34

Я працюю з наступною системою:

Network Data Feed -> Third Party Nio Library -> My Objects via adapter pattern

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

Проблема:

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

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

Питання:

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

Зараз у мене немає процесу моїх інтеграційних тестів. Тестування по суті: спробуйте зберегти одиничне тестування, додайте більше тестів, коли щось порушиться, потім розгорніть на мій тестовий сервер і переконайтесь, що все здається здоровим, а потім розгорніть до виробництва. Цей випуск часової мітки пройшов одиничні тести, тому що макети були створені неправильно, потім він пройшов тест інтеграції, оскільки він не викликав негайних, очевидних проблем. У мене немає відділу якості.


3
Чи можете ваш "записати" реальний канал даних і "відтворити" його згодом у бібліотеці сторонніх розробників?
Ідан Ар'є

2
Хтось міг написати книгу про подібні проблеми. Насправді Майкл Пірс написав саме цю книгу: c2.com/cgi/wiki?WorkingEffectivelyWithLegacyCode У ній він описує низку методів розбиття складних залежностей, щоб код міг стати більш перевіреним.
cbojar

2
Адаптер навколо бібліотеки третьої сторони? Так, саме це я рекомендую. Ці тестові одиниці не поліпшать ваш код. Вони не зроблять його більш надійним або більш ретельним. Ви лише частково копіюєте чужий код у той момент; у цьому випадку ви дублюєте погано записаний код із звуку цього. Це чисті збитки. Деякі відповіді пропонують зробити тестування на інтеграцію; це гарна ідея, якщо ви просто хочете: "Це працює?" перевірка санітарності. Хороше тестування важке, і воно вимагає стільки ж майстерності та інтуїції, скільки й хороший код.
jpmc26

4
Ідеальна ілюстрація зла вбудованих. Чому не бібліотека повертає Timestampклас (що містить будь-яке представлення , що вони хочуть) і забезпечує названі методи ( .seconds(), .milliseconds(), .microseconds(), .nanoseconds()) і, звичайно ж, названі конструктори. Тоді б проблем не було.
Матьє М.

2
Тут приходить в голову приказка "всі проблеми кодування можуть бути вирішені шаром непрямості (за винятком, звичайно, проблеми занадто багато шарів непрямості)"
Dan Pantry

Відповіді:


27

Це здається, що ви вже робите належну ретельність. Але ...

На самому практичному рівні завжди включайте в свою програму гарну жменю інтегральних тестів "повного циклу" для власного коду та пишіть більше тверджень, ніж ви вважаєте, що вам потрібно. Зокрема, у вас має бути кілька тестів, які виконують повний цикл create-read- [do_stuff] -validate.

[TestMethod]
public void MyFormatter_FormatsTimesCorrectly() {

  // this test isn't necessarily about the stream or the external interpreter.
  // but ... we depend on them working how we think they work:
  var stream = new StreamThingy();
  var interpreter = new InterpreterThingy(stream);
  stream.Write("id-123, some description, 12345");

  // this is what you're actually testing. but, it'll also hiccup
  // if your 3rd party dependencies introduce a breaking change.
  var formatter = new MyFormatter(interpreter);
  var line = formatter.getLine();
  Assert.equal(
    "some description took 123.45 seconds to complete (id-123)", line
  );
}

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

Припустимо, вам потрібно зрозуміти та залежати від того, як аналізатор JSON інтерпретує кожен "тип" у рядку JSON. Корисно і банально включити щось подібне у свій набір:

[TestMethod]
public void JSONParser_InterpretsTypesAsExpected() {
  String datastream = "{nbr:11,str:"22",nll:null,udf:undefined}";
  var o = (new JSONParser()).parse(datastream);

  Assert.equal(11, o.nbr);
  Assert.equal(Int32.getType(), o.nbr.getType());
  Assert.equal("22", o.str);
  Assert.equal(null, o.nll);
  Assert.equal(Object.getType(), o.nll.getType());
  Assert.isFalse(o.KeyExists(udf));
}

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

І значною мірою це нормально.

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


Фу, хотілося б, щоб це було так само просто, як подавати рядок json в бібліотеку. Це не. Я не можу зробити еквівалент (new JSONParser()).parse(datastream), оскільки вони захоплюють дані безпосередньо з а, NetworkInterfaceа всі класи, які роблять фактичний аналіз, є приватними пакетами та захищаються.
durron597

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

@ durron597 О, це майже ніколи не буває. Але ви часто можете підробляти базове джерело даних - як у прикладі першого коду. ... Суть у тому, що: коли це можливо, робіть тести інтеграції з повним циклом, перевіряйте своє розуміння бібліотеки, коли це можливо, і просто пам’ятайте, що ви все одно будете пускати помилок у дику природу. І ваші сторонні постачальники повинні відповідати за те, що вони роблять невидимими, порушуючи зміни.
svidgen

@ durron597 Я не знайомий з NetworkInterface... це щось, до чого можна подати дані, підключивши інтерфейс до порту на localhost чи щось таке?
svidgen

NetworkInterface. Це об'єкт низького рівня для прямої роботи з мережевою картою та відкриття на ній розеток тощо.
durron597

11

Коротка відповідь: Важко. Ви, мабуть, відчуваєте, що немає хороших відповідей, і це тому, що немає простих відповідей.

Довга відповідь: як каже @ptyx , вам потрібні системні тести та інтеграційні тести, а також одиничні тести:

  • Тестові одиниці швидко і легко виконуються. Вони ловлять помилки в окремих розділах коду та використовують макети, щоб зробити їх можливим. За необхідності вони не можуть впізнати невідповідності між фрагментами коду (наприклад, мілісекунд проти наносекунд).
  • Інтеграційні тести та системні тести виконуються повільно (er) та важко (er), але вдається отримати більше помилок.

Деякі конкретні пропозиції:

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

Я бачив програмування, описане як діяльність щодо вивчення проблеми та простору рішення. Отримати все досконалим достроково може не можливо, але ви можете дізнатися після цього. ("Я виправив обробку часових міток у кількох місцях, але пропустив один. Чи можу я змінити типи даних або класи, щоб зробити обробку часових міток більш чіткими і важче пропустити, або зробити її більш централізованою, щоб у мене було лише одне місце змінити? Чи можу я змінити мої тести, щоб перевірити більше аспектів обробки часових міток? Чи можу я спростити своє тестове середовище, щоб зробити це простіше у майбутньому? Чи можу я уявити собі якийсь інструмент, який би полегшив це, і якщо так, чи можна знайти такий інструмент в Google? "Тощо)


7

Я оновив версію бібліотеки… яка… спричинила зміну часових позначок (які стороння бібліотека повертає як long), змінити з мілісекунд після епохи на наносекунд після епохи.

Це не помилка в бібліотеці

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

Скажімо, замість цього бібліотека повертає тип MillisecondsSinceEpochпростої обгортки, що містить long. Коли вони змінили його на NanosecondsSinceEpochзначення, ваш код не вдалося б скомпілювати і, очевидно, вказав би на місця, де потрібно внести зміни. Зміна не могла мовчки зіпсувати вашу програму.

Ще краще було б TimeSinceEpochоб'єкт, який міг би адаптувати його інтерфейс, оскільки була додана більш точна, наприклад додавання #toLongNanosecondsметоду поряд із #toLongMillisecondsметодом, не вимагаючи жодних змін у вашому коді.

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


2
@ durron597 Я все одно заперечую, що це помилка. Поза відсутністю документації, навіщо взагалі змінювати очікувану поведінку? Чому б не новий метод, який забезпечує нову точність, і нехай старий метод все-таки забезпечує міліс? І чому б не надати компілятору спосіб сповістити вас про зміну типу повернення? Не потрібно багато, щоб зробити це набагато зрозумілішим, не тільки в документації, але і в самому коді.
cbojar

1
@gbjbaanb, "що у них погана практика випуску" мені здається помилкою
Артуро Торрес Санчес

2
@gbjbaanb Стороння бібліотека [повинна] укласти "контракт" зі своїми користувачами. Порушення цього договору - будь то документально підтверджений чи ні - можна / слід вважати помилкою. Як говорили інші, якщо вам потрібно щось змінити, додайте до контракту нову функцію / метод (див. Усі ...Ex()методи в Win32API). Якщо це неможливо, "розірвати" договір шляхом перейменування функції (або її типу повернення) було б краще, ніж змінити поведінку.
TripeHound

1
Це помилка в бібліотеці. Використання наносекунд у довгий час штовхає його.
Джошуа

1
@gbjbaanb Ви кажете, що це не помилка, оскільки це призначена поведінка, навіть якщо вона несподівана. У цьому сенсі це не помилка реалізації , але це помилка. Це можна назвати дефектом дизайну або помилкою взаємодії . Недоліки полягають у тому, що вона виявляє примітивну одержимість довгими, а не явними одиницями, її абстракція є протікаючою, оскільки вона експортує деталі її внутрішньої реалізації (що дані зберігаються як довга певної одиниці), і що вона порушує принцип найменшого здивування з тонкою зміною одиниці.
cbojar

5

Вам потрібні інтеграційні та системні тести.

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

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

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


2

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

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

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


1

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

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


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

1
@ durron597 Тоді, можливо, шар недостатньо ізолює бібліотеку від решти вашої програми. Якщо ви виявите, що вам важко перевірити цей шар, можливо, вам потрібно буде спростити поведінку і сильніше ізолювати основні дані.
cbojar

Що сказав @cbojar Крім того, дозвольте повторити щось, що, можливо, залишилося непоміченим у вищенаведеному тексті: assertКлючове слово (або функція чи об'єкт, залежно від того, якою мовою ви користуєтесь) - ваш друг. Я не говорю про твердження в тестах одиниці / інтеграції, я кажу, що шар ізоляції повинен бути дуже важким із твердженнями, стверджуючи, що все можна стверджувати про поведінку бібліотеки.
Майк Накіс

Ці твердження не обов'язково виконуються на виробничих прогонах, але вони виконуються під час тестування, маючи перегляд вашого ізоляційного шару і, таким чином, зможете переконатися (наскільки це можливо) інформацію, яку ваш шар отримує з бібліотеки звук.
Майк Накіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.