Як ви пишете одиничні тести на код із важко передбачуваними результатами?


124

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

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

Чи відомі методи написання одиничних тестів та застосування TDD, коли результат перевіреного коду важко передбачити?

(Реальний) приклад коду з важко передбачуваними результатами:

Функція, weightedTasksOnTimeяка, враховуючи кількість робіт, виконаних за день workPerDayв діапазоні (0, 24], поточний час initialTime> 0, і список завдань taskArray; кожне з часом заповнити властивість time> 0, термін dueі значення важливості importance; повертається нормоване значення в діапазоні [0, 1], що представляє важливість завдань, які можна виконати до їх dueдати, якщо кожне завдання, якщо виконане в порядку, заданому taskArray, починаючи з initialTime.

Алгоритм реалізації цієї функції відносно простий: ітерайте над завданнями в taskArray. Для кожного завдання додайте timeдо initialTime. Якщо новий час < due, додайте importanceдо акумулятора. Час регулюється за допомогою зворотного робочого дня. Перш ніж повернути акумулятор, розділіть їх на суму важливих завдань для нормалізації.

function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time * (24 / workPerDay)
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator / totalImportance(taskArray)
}

Я вважаю, що вищезазначена проблема може бути спрощена, зберігаючи її суть, видаляючи workPerDayта вимогу нормалізації, щоб:

function weightedTasksOnTime(initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator
}

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


4
Чи можете ви навести простий приклад функції, результат якої важко передбачити?
Роберт Харві

62
FWIW ви не тестуєте алгоритм. Імовірно, це правильно. Ви тестуєте реалізацію. Опрацювання вручну часто добре, як паралельна конструкція.
Крістіан Н


7
Бувають ситуації, коли алгоритм не може бути обґрунтовано перевірений одиницею, - наприклад, якщо час його виконання становить кілька днів / місяців Це може статися при вирішенні завдань NP. У цих випадках може бути більш доцільним надати офіційний доказ правильності коду.
Халк

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

Відповіді:


251

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

Друге - це перевірки санітарності. Це перевірки, які ви робите там, де не знаєте, чи відповідь правильна , але ви точно знаєте, чи не так . Це такі речі, як час повинен рухатися вперед, значення повинні знаходитися в розумному діапазоні, відсотки повинні складати до 100 і т.д.

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


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

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

7
Перевірка обгрунтованості часто робить хороші цілі для тестів на основі властивостей, таких як QuickCheck
jk.

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

5
@iFlo Не впевнений, чи ти жартував, але зворотний зворот уже є. Варто усвідомити, що невдача тесту може бути проблемою в зворотній функції
lucidbrot

80

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

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


32
Метаморфічні відносини - це конкретний приклад тестування на основі властивостей , який загалом є корисним інструментом для таких ситуацій
Dannnno

38

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

Він виявить, чи змінився алгоритм (або дані, від яких залежить)

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


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

21

Так само, як ви пишете одиничні тести для будь-якого іншого виду коду:

  1. Знайдіть кілька репрезентативних тестових випадків і випробуйте їх.
  2. Знайдіть крайові корпуси та протестуйте їх.
  3. Знайдіть умови помилок та протестуйте їх.

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

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


2
Для недетермінованих алгоритмів ви можете зберегти насіння RNG або знущатися над ним, використовуючи фіксовану послідовність або детерміністський ряд з низькою невідповідністю, наприклад, послідовність
Халтона

14
@PaintingInAir Якщо неможливо перевірити вихід алгоритму, може алгоритм бути навіть неправильним?
WolfgangGroiss

5
Unless your code involves some random elementТрюк тут полягає в тому, щоб зробити ваш генератор випадкових чисел введеною залежністю, тож ви можете замінити його на генератор чисел, який дає точний результат, який ви хочете. Це дозволяє вам ще раз тестувати ще раз - підраховуючи згенеровані числа як вхідні параметри. not deterministic (i.e. it won't produce the same output given the same input)Оскільки одиничний тест повинен починатися з контрольованої ситуації, він може бути недетермінованим лише у тому випадку, якщо він має випадковий елемент - який ви можете потім ввести. Я не можу тут думати про інші можливості.
Flater

3
@PaintingInAir: або. Мій коментар стосується як швидкого виконання, так і швидкого написання тесту. Якщо вам потрібно три дні, щоб обчислити один приклад вручну (припустимо, ви використовуєте найшвидший доступний метод, який не використовує код) - то три дні - це потрібно. Якщо ви замість цього базували очікуваний результат тесту на фактично самому коді, то тест посягає на себе. Це як робити if(x == x), це безглуздо порівняння. Вам потрібні два результати ( фактичні : походить від коду; очікуваний : походить від ваших зовнішніх знань), щоб бути незалежними один від одного.
Flater

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

17

Оновлення завдяки опублікованим коментарям

Оригінальну відповідь було видалено заради стислості - її можна знайти в історії редагування.

PaintingInAir Для контексту: як підприємець, так і академік, більшість алгоритмів, які я розробляю, не вимагає ніхто, крім мене. Приклад, наведений у запитанні, є частиною оптимізатора, що не містить похідних, щоб досягти максимальної якості впорядкування завдань. З точки зору того, як я внутрішньо описав потребу в прикладі, функціонує: "Мені потрібна об'єктивна функція для максимізації важливості завдань, які виконуються вчасно". Однак все ще існує великий розрив між цим запитом та впровадженням одиничних тестів.

По-перше, TL; DR, щоб уникнути інакшої тривалої відповіді:

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

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

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

Навіть якщо ви і замовник, і кухар, все одно є важливе розмежування між:

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

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

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

Наприклад, замовник хоче, щоб він працював так [це] . Однак розробник неправильно розуміє, і він пише код, який робить [що] .

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

Якщо ви хочете перевірити очікування замовника (вимоги бізнесу), це потрібно зробити окремим (і пізнішим) кроком.

Простий робочий процес з розробки, щоб показати, коли слід запускати ці тести:

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

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

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

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

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

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

Я був би радий детальніше розглянути питання про ситуацію "Якби ти сам придумав алгоритм", оскільки я думаю, що саме ця ситуація, швидше за все, представлятиме проблеми. Особливо в ситуаціях, коли не наводяться приклади "якщо А, то Б, інакше С". (ps Я не є першокласником)
PaintingInAir

@PaintingInAir: Але я не можу дуже детально розглянути це, оскільки це залежить від вашої ситуації. Якщо ви вирішили створити цей алгоритм, ви, очевидно, зробили це для надання певної функції. Хто просив вас це зробити? Як вони описали своє прохання? Чи сказали вони вам, що їм потрібно було статися за певних сценаріїв? (цю інформацію я називаю "аналізом" у своїй відповіді) Яке б пояснення ви не отримали (що призвело до створення алгоритму), можна використовувати для перевірки, чи алгоритм працює відповідно до запиту. Коротше кажучи, будь-що, крім кодового / створеного алгоритмом, може бути використане.
Flater

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

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

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

9

Тестування власності

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

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

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


1
Я б запропонував, можливо, додати у свій приклад повторне множення чогось дещо більш конкретного, наприклад, генерування випадкових квартетів (a, b, c) та підтвердження того, що (ab) (cd) виходить (ac-ad) - (bc-bd). Операція множення може бути досить зламана і все ще підтримувати правило (негативні моменти, негативний прирік позитивне), але правило розподілу передбачає конкретні результати.
supercat

4

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

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

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

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

  3. Використовуйте сукупні властивості для перевірки чистоти. Наприклад, скажімо, у вас є калькулятор ймовірності; ви можете не знати, якими мають бути окремі результати, але ви знаєте, що всі вони повинні скласти до 100%.

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


Для 3. дозвольте тут деякі помилки округлення. Можливо, що ваша загальна сума становить 100,000001% або аналогічно близькі, але не точні показники.
Flater

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

6
@flater, як правило, у вас є інші вимоги, а також правильність, якою груба сила не відповідає. наприклад, продуктивність.
Еван

1
@flater Мені б не хотілося використовувати ваш сорт, найкоротший шлях, шаховий двигун тощо, якщо ви вірите в це. Але ідентифікатор повністю грати в азартні ігри в вашій помилки округлення дозволило казино весь день
Ewan

3
@flater Ви подаєте у відставку, коли потрапите в гру короля пішака? тільки тому, що вся гра не може бути жорстокою вимушеною, не означає, що не може бути окремої позиції Тільки тому, що ви жорстоко змушуєте правильний найкоротший шлях до однієї мережі, це зовсім не означає, що ви знаєте найкоротший шлях у всіх мережах
Ewan

2

TL; DR

Перейдіть до розділу "порівняльне тестування", щоб отримати поради, які не містять інших відповідей.


Початок

Почніть з тестування випадків, які слід відхилити алгоритмом (наприклад, нуль або мінус workPerDay) та випадків, які є тривіальними (наприклад, порожній tasksмасив).

Після цього потрібно спочатку перевірити найпростіші випадки. Для tasksвведення нам потрібно перевірити різну довжину; його слід вистачити для тестування 0, 1 і 2 елементів (2 належать до категорії "багато" для цього тесту).

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

Порівняльне тестування

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

Відвали

Іноді мені доводиться вдаватися до довгого коментаря, що показує обчислений вручну результат у кроках, відповідних специфікації (такий коментар зазвичай довший, ніж тестовий випадок). Найгірший випадок, коли вам доведеться підтримувати сумісність з попередньою реалізацією іншою мовою або для іншого середовища. Іноді просто потрібно позначити дані тесту чимось подібним /* derived from v2.6 implementation on ARM system */. Це не дуже задовільно, але може бути прийнятним як тест на вірність при перенесенні, або як короткочасний милицю.

Нагадування

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

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

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


2

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

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

Тоді, очевидно, внесіть до крайніх справ, як-от (у вашому прикладі) порожній список завдань; такі речі.

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

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

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


2

Інші відповіді хороші, тому я спробую познайомитися з деякими моментами, які вони колективно пропустили досі.

Я написав (і ретельно протестував) програмне забезпечення для обробки зображень за допомогою синтетичного апертурного радара (SAR). Це науково / числовий характер (тут багато геометрії, фізики та математики).

Кілька порад (для загального наукового / числового тестування):

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

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

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

EXPECT_TRUE(register(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.

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

Ваш конкретний випадок

1) Перевірте, що порожній taskArray повертає 0 (відомий ствердження).

2) Генерація випадкових введення таким чином, що task.time > 0 , task.due > 0, і task.importance > 0 для всіх task х, і стверджують , результат більше , ніж 0 (грубі стверджують, випадковим чином входу) . Вам не потрібно сходити з розуму і генерувати випадкові насіння, ваш алгоритм просто недостатньо складний, щоб гарантувати це. Є приблизно 0 шансів, що це окупиться: просто тримайте тест простим.

3) Перевірте, якщо task.importance == 0 для всіх task s, то результат є 0 (відоме ствердження)

4) Інші відповіді на це стосуються, але це може бути важливо для вашого конкретного випадку: Якщо ви робите API, який слід споживати користувачам поза вашою командою, вам потрібно перевірити вироджені випадки. Наприклад, якщо workPerDay == 0, переконайтеся, що ви кинули чудову помилку, яка повідомляє користувачеві, що недійсне введення. Якщо ви не створюєте API, і це лише для вас та вашої команди, ви, ймовірно, можете пропустити цей крок і просто відмовитись викликати його із виродженим випадком.

HTH.


1

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

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

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


1

Деякі інші відповіді тут дуже хороші:

  • Тестова основа, кромка та кутовий корпус
  • Виконуйте перевірки здорового стану
  • Проведіть порівняльні тести

... Я додам кілька інших тактик:

  • Розв’яжіть проблему.
  • Доведіть алгоритм поза кодом.
  • Перевірте, чи алгоритм [доведено зовні] реалізовано як задумано.

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

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


0

Це може здатися дещо ідеалістичною відповіддю, але це допомагає виявити різні види тестування.

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

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

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


0

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

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

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

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

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


0

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

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

  1. "Випадкові" введення
  2. Ітерація в різних діапазонах даних
  3. Побудова тестових випадків із наборів меж
  4. Все вищесказане.

Наприклад, якщо функція приймає три параметри кожного з дозволеним діапазоном введення [-1,1], протестуйте всі комбінації кожного параметра, {-2, -1.01, -1, -0.99, -0.5, -0.01, 0,0.01 , 0,5,0,99,1,1.01,2, ще кілька випадкових у (-1,1)}

Коротше кажучи: іноді низька якість може бути субсидована кількістю.

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