Якщо SavePeople () має бути перевірений
Так, слід. Але спробуйте написати свої тестові умови таким чином, що це не залежить від реалізації. Наприклад, перетворивши приклад використання в тестовий пристрій:
function testSavePeople() {
myDataStore = new Store('some connection string', 'password');
myPeople = ['Joe', 'Maggie', 'John'];
savePeople(myDataStore, myPeople);
assert(myDataStore.containsPerson('Joe'));
assert(myDataStore.containsPerson('Maggie'));
assert(myDataStore.containsPerson('John'));
}
Цей тест робить кілька речей:
- він перевіряє контракт функції
savePeople()
- це не дбає про реалізацію
savePeople()
- він документує приклад використання
savePeople()
Візьміть до уваги, що ви все ще можете глузувати / заглушувати / підробляти сховище даних. У цьому випадку я б не перевіряв наявність явних функціональних дзвінків, а результат операції. Таким чином мій тест готується до майбутніх змін / рефакторів.
Наприклад, реалізація сховища даних може надавати saveBulkPerson()
метод у майбутньому - тепер зміна в застосуванні savePeople()
до використання saveBulkPerson()
не порушить одиничний тест до тих пір, як saveBulkPerson()
працює, як очікувалося. І якщо saveBulkPerson()
якимось чином не працює так, як очікувалося, ваш одиничний тест це вловить.
чи такі тести означатимуть тестування вбудованої конструкції мови forEach?
Як було сказано, спробуйте перевірити очікувані результати та інтерфейс функцій, а не для впровадження (якщо ви не робите тести інтеграції - тоді для отримання певних функціональних викликів може бути корисним). Якщо існує декілька способів реалізації функції, усі вони повинні працювати з вашим тестовим пристроєм.
Щодо Вашого оновлення питання:
Тест на зміни стану! Наприклад, частина тіста буде використана. Згідно з вашою реалізацією, запевняйте, що кількість використаного вмісту dough
відповідає pan
або стверджує, що dough
витрачена. Стверджуйте, що pan
містять файли cookie після виклику функції. Стверджуйте, що oven
порожній / у тому ж стані, що і раніше.
Для додаткових тестів перевірте кращі випадки: Що станеться, якщо oven
перед викликом параметр не порожній? Що станеться, якщо їх недостатньо dough
? Якщо pan
вже заповнене?
Ви повинні мати можливість вивести всі необхідні дані для цих випробувань із самих предметів тіста, сковороди та духовки. Не потрібно фіксувати виклики функцій. Ставтесь до функції так, ніби її реалізація була б недоступною для вас!
Насправді більшість користувачів TDD пишуть свої тести ще до того, як вони записують функцію, тому вони не залежать від реальної реалізації.
Для вашого останнього доповнення:
Коли користувач створює новий обліковий запис, має відбутися ряд речей: 1) в базі даних потрібно створити нову запис користувача; 2) надіслати електронну адресу привітання; 3) IP-адресу користувача потрібно записати для шахрайства. цілей.
Отже, ми хочемо створити метод, який поєднує всі кроки "нового користувача":
function createNewUser(validatedUserData, emailService, dataStore) {
userId = dataStore.insertUserRecord(validateduserData);
emailService.sendWelcomeEmail(validatedUserData);
dataStore.recordIpAddress(userId, validatedUserData.ip);
}
Для такої функції, як я би знущався / заглушував / підробляв (що здається більш загальним) параметри dataStore
та emailService
. Ця функція не робить жодних переходів стану за будь-яким параметром самостійно, вона делегує їх методам деяких із них. Я б спробував переконатися, що виклик функції виконував 4 речі:
- він вставив користувача в сховище даних
- він надіслав (або принаймні назвав відповідний метод) вітальним листом
- він записував IP користувачів у сховище даних
- він делегував будь-яке виняток / помилку, з якою він стикався (за наявності)
Перші 3 перевірки можна зробити з макетами, заглушками або підробками dataStore
та emailService
(ви дійсно не хочете надсилати електронні листи при тестуванні). Оскільки я повинен був переглянути це за деякими коментарями, ось ці відмінності:
- Підробка - це предмет, який поводиться так само, як оригінал і певною мірою не відрізняється. Його код зазвичай може бути повторно використаний через тести. Наприклад, це може бути проста база даних в пам'яті для обгортки бази даних.
- Заглушка просто реалізує стільки, скільки потрібно для виконання необхідних операцій цього тесту. У більшості випадків заглушка є специфічною для тесту чи групи тестів, що вимагає лише невеликого набору методів оригіналу. У цьому прикладі це може бути
dataStore
те, що просто реалізує відповідну версію insertUserRecord()
і recordIpAddress()
.
- Макет - це об’єкт, який дозволяє перевірити, як він використовується (найчастіше, дозволяючи оцінювати виклики його методів). Я б спробував використовувати їх помірно в тестових одиницях, оскільки, використовуючи їх, ви насправді намагаєтеся перевірити реалізацію функції, а не прихильність до її інтерфейсу, але вони все ще мають своє використання. Існує безліч фреймворків, які допоможуть вам створити потрібний макет.
Зауважте, що якщо будь-який із цих методів видає помилку, ми хочемо, щоб помилка передавалася до викликового коду, щоб він міг обробляти помилку, як вважає за потрібне. Якщо його викликає код API, він може перевести помилку у відповідний код відповіді HTTP. Якщо його викликає веб-інтерфейс, він може перевести помилку у відповідне повідомлення, яке відображатиметься користувачеві тощо. Справа в тому, що ця функція не знає, як поводитися з помилками, які можуть бути викинуті.
Очікувані винятки / помилки є дійсними тестовими випадками: Ви підтверджуєте, що у випадку, якщо така подія трапиться, функція поводиться так, як ви очікували б. Цього можна досягти, дозволяючи відповідний макет / підроблений / заглушений предмет кидати за бажанням.
Суть моєї плутанини полягає в тому, що для тестування такої функції видається необхідним повторити точну реалізацію в самому тесті (вказавши, що методи викликаються макетами в певному порядку), і це здається неправильним.
Іноді це доводиться робити (хоча ви здебільшого дбаєте про це в інтеграційних тестах). Частіше існують інші способи перевірити очікувані побічні ефекти / зміни стану.
Перевірка точних викликів функцій спричиняє досить крихкі тестові одиниці: Лише невеликі зміни оригінальної функції призводять до їх відмови. Це можна бажати чи ні, але це вимагає змін у відповідних тестах одиниць під час зміни функції (будь то рефакторинг, оптимізація, виправлення помилок, ...).
На жаль, у цьому випадку тест блоку втрачає частину своєї достовірності: оскільки він був змінений, він не підтверджує функцію після того, як зміна поводиться так само, як і раніше.
Для прикладу розглянемо, хто додає виклик oven.preheat()
(оптимізація!) У вашому прикладі випічки файлів cookie:
- Якщо ви знущалися над духовним об’єктом, він не очікує цього виклику і не завершить тест, хоча спостережлива поведінка методу не змінилася (сподіваємось, у вас все ще є сковорода куки).
- Заглушка може або не може вийти з ладу, залежно від того, додали ви лише тестовані методи або весь інтерфейс деякими фіктивними методами.
- Підробка не повинна провалюватися, оскільки вона має реалізувати метод (відповідно до інтерфейсу)
У своїх одиничних тестах я намагаюся бути якомога загальнішими: Якщо реалізація змінюється, але видима поведінка (з точки зору абонента) все одно, мої тести повинні пройти. В ідеалі, єдиний випадок, коли мені потрібно змінити існуючий тест одиниці, повинен бути виправлення помилок (тесту, а не функції, що перевіряється).