безперервна інтеграція наукового програмного забезпечення


22

Я не інженер програмного забезпечення. Я аспірант в галузі геознавства.

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

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

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

У мене є кілька запитань, де я хотів би отримати поради:

Перш за все коротке пояснення того, як працює програмне забезпечення:

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

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

Тепер перейдемо до моїх запитань:

  1. одиничні тести, інтеграційні тести, тести на кінці? : У моєму програмному забезпеченні зараз близько 30 000 рядків коду з сотнями функцій і ~ 80 класів. Мені здається дивним почати писати одиничні тести для сотень функцій, які вже реалізовані. Тому я подумав про те, щоб просто створити кілька тестових випадків. Підготуйте 10-20 різних .xml файлів і дозвольте запуску програмного забезпечення. Я думаю, це те, що називається тестами в кінці? Я часто читаю, що ви не повинні цього робити, але, можливо, це все нормально, якщо у вас вже є робоче програмне забезпечення? Або просто проста дурна ідея спробувати додати CI до вже працюючого програмного забезпечення.

  2. Як ви пишете одиничні тести, якщо параметри функції важко створити? припустимо, у мене є функція, double fun(vector<Class_A> a, vector<Class_B>)і зазвичай мені потрібно спершу прочитати в декількох текстових файлах, щоб створити об'єкти типу Class_Aта Class_B. Я думав про створення деяких фіктивних функцій, таких як Class_A create_dummy_object()без читання в текстових файлах. Я також думав про реалізацію якоїсь серіалізації . (Я не планую тестувати створення об’єктів класу, оскільки вони залежать лише від декількох текстових файлів)

  3. Як написати тести, якщо результати сильно відрізняються? Моє програмне забезпечення використовує великі імітації Монте-Карло та працює ітераційно. Зазвичай у вас є ~ 1000 ітерацій, і на кожній ітерації ви створюєте ~ 500-20 000 екземплярів об'єктів на основі моделювання Монте-Карло. Якщо лише один результат однієї ітерації трохи інший, то всі наступні ітерації зовсім інші. Як ви вирішите цю ситуацію? Я вважаю, що це є важливим моментом у порівнянні з тестами, оскільки кінцевий результат дуже мінливий?

Будь-які інші поради з CI високо оцінені.



1
Як ви знаєте, що ваше програмне забезпечення працює правильно? Чи можете ви знайти спосіб автоматизації цієї перевірки, щоб ви могли запускати її при кожній зміні? Це має стати вашим першим кроком при впровадженні ІС у існуючий проект.
Барт ван Інген Шенау

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

Відповіді:


23

Тестування наукового програмного забезпечення є важким, як через складну тематику, так і через типові процеси наукової розробки (ака. Хакувати його до тих пір, поки воно не спрацює, що зазвичай не призводить до перевіреної конструкції). Це трохи іронічно, враховуючи, що наука повинна бути відтвореною. Зміни в порівнянні з "нормальним" програмним забезпеченням полягають не в тому, чи корисні тести (так!), А які види тестування є відповідними.

Поводження з випадковістю: ОБОВ'ЯЗКОВО всі програми вашого ПО повинні бути відтвореними. Якщо ви використовуєте методи Монте-Карло, ви повинні дати можливість надати конкретне насіння для генератора випадкових чисел.

  • Це легко забути, наприклад, використовуючи rand()функцію C, яка залежить від глобального стану.
  • В ідеалі генератор випадкових чисел передається як явний об'єкт через ваші функції. randomСтандартний заголовок бібліотеки C ++ 11 значно покращує це.
  • Замість обміну випадковим станом між модулями програмного забезпечення я вважаю корисним створити другий RNG, який виводиться випадковим числом з першого RNG. Потім, якщо кількість запитів до RNG іншим модулем змінюється, послідовність, породжена першим RNG, залишається однаковою.

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

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

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

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

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

Тестування вручну: Спеціально для складних проблемних доменів ви не зможете все протестувати автоматично. Наприклад, я зараз працюю над проблемою стохастичного пошуку. Якщо я перевіряю, що моє програмне забезпечення завжди дає однаковий результат, я не можу його покращити, не порушивши тести. Натомість я полегшив тестування вручну : запускаю програмне забезпечення з фіксованим насінням та отримую візуалізаціюрезультату (залежно від ваших уподобань, R, Python / Pyplot та Matlab - це полегшує отримання якісних візуалізацій ваших наборів даних). Я можу використовувати цю візуалізацію, щоб переконатися, що все не пішло жахливо. Аналогічно, відстеження прогресу вашого програмного забезпечення за допомогою виходу журналу може бути життєздатною технікою ручного тестування, принаймні, якщо я можу вибрати тип подій, які потрібно реєструвати.


7

Мені здається дивним почати писати одиничні тести для сотень функцій, які вже реалізовані.

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

Як ви пишете одиничні тести, якщо параметри функції важко створити?

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

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

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

Як написати тести, якщо результати сильно відрізняються?

Пара порад:

1) Використовуйте обертання (або загалом тестування на основі властивостей). Що таке ффт [1,2,3,4,5]? Не маю уявлення. Що ifft(fft([1,2,3,4,5]))? Якщо має бути [1,2,3,4,5](або близько до нього, можуть виникнути помилки з плаваючою точкою).

2) Використовуйте "відомі" твердження. Якщо ви пишете визначальну функцію, може бути важко сказати, що таке визначник матриці 100x100. Але ви знаєте, що визначник матриці ідентичності дорівнює 1, навіть якщо він 100x100. Ви також знаєте, що функція повинна повертати 0 на неперевернутій матриці (як 100x100, повна всіх 0s).

3) Використовуйте грубі твердження замість точних тверджень. Нещодавно я написав якийсь код, який реєстрував два зображення, генеруючи крапки, які створюють відображення між зображеннями і роблять основу між ними, щоб вони відповідали. Він може зареєструватися на рівні пікселя. Як ви можете це протестувати? Такі речі:

EXPECT_TRUE(reg(img1, img2).size() < min(img1.size(), img2.size()))

оскільки ви можете зареєструватися лише на деталях, що перекриваються, зареєстроване зображення має бути меншим або рівним вашому найменшому зображенню), а також:

scale = 255
EXPECT_PIXEL_EQ_WITH_TOLERANCE(reg(img, img), img, .05*scale)

оскільки зображення, зареєстроване для себе, повинно бути ЗАКРИТОМ для себе, але ви можете відчути трохи більше помилок з плаваючою комою через алгоритм, що підходить, тому просто перевірте, чи кожен піксель має +/- 5% від допустимого діапазону (0-255 це загальний діапазон, відтінки сірого). Повинен бути хоча б однакового розміру. Ви навіть можете просто перевірити дим (тобто зателефонувати і переконатися, що він не виходить з ладу). Як правило, ця методика краща для великих тестів, коли кінцевий результат не можна (легко) обчислити апріорі до запуску тесту.

4) Використовуйте АБО ЗБЕРЕЖИТИ насіння випадкових чисел для RNG.

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

HTH.


2
  1. Види тесту

    • Мені здається дивним почати писати одиничні тести для сотень функцій, які вже реалізовані

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

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

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

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

    • Так, додавання CI до існуючого програмного забезпечення є розумним і нормальним.

  2. Як писати одиничні тести

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

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

  3. Змінні результати

    Ви повинні мати кілька інваріантів для результату. Перевірте це, а не одне числове значення.

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


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

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

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

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

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

Деякі поради:

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

1

У відповіді перед Амоном вже згадувалися деякі дуже важливі моменти. Дозвольте додати ще:

1. Відмінності між розробкою наукового програмного забезпечення та комерційним програмним забезпеченням

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

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

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

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

  • Ду, у вас є час і ресурси?
  • Яка довгострокова перспектива програмного забезпечення? Що буде з програмним забезпеченням, коли ви закінчите свою роботу та покинете університет?

2. Тести з кінця в кінець

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

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

Звичайно, для отримання відтворюваних результатів слід використовувати те саме насіння для свого генератора випадкових чисел, як уже писав Амон .

Приклади повинні охоплювати типові результати вашого програмного забезпечення. Сюди також слід включати крайові регістри простору параметрів та числові алгоритми.

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

3. Безперервна інтеграція

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

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

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

До речі, ви використовуєте систему контролю версій?

4. Тестування чисельних алгоритмів

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

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

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


0

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

Це сказало, що цінується:

  • це найкращий на той час метод з точки зору алгоритму?
  • наскільки легко переносити на різні обчислювальні платформи (різні середовища HPC, ароматизатори ОС тощо)
  • надійність - вона працює на моєму наборі даних?

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

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