Коротка відповідь. На live coding тести – це не формальність, а найшвидший спосіб показати інженерне мислення. Навіть якщо середовище співбесіди не має тест-раннера, кілька рядків із console.assert демонструють: ти думаєш про крайні випадки до того, як інтерв'юер про них спитає. Нижче – мінімальний патерн без фреймворка, той самий підхід у синтаксисі vitest/jest і готові тест-кейси для двох класичних задач.
Чому тести підвищують оцінку
Інтерв'юер за 40 хвилин оцінює не лише «працює/не працює», а як ти мислиш. Кандидат, який після happy path каже «тепер перевірю порожній масив і від'ємні значення» і робить це кодом, закриває одразу три пункти оцінки: розуміння вимог, увагу до країв і звичку до самоперевірки. Це працює на будь-якому рівні – від Junior до Senior, різниця лише в глибині кейсів.
Мінімальний патерн без фреймворка
У більшості онлайн-редакторів співбесід немає jest. Достатньо console.assert:
function groupBy(items, keyFn) {
const groups = new Map();
for (const item of items) {
const key = keyFn(item);
const bucket = groups.get(key) ?? [];
bucket.push(item);
groups.set(key, bucket);
}
return groups;
}
// Тести: assert мовчить, якщо все добре, і кричить, якщо ні
const byLength = groupBy(['ай', 'бо', 'кіт'], (word) => word.length);
console.assert(byLength.get(2).length === 2, 'два слова довжини 2');
console.assert(byLength.get(3).length === 1, 'одне слово довжини 3');
const empty = groupBy([], (x) => x);
console.assert(empty.size === 0, 'порожній вхід -> порожній результат');Правило: один assert – одне твердження з підписом. Підпис важливий: коли щось упаде, ти одразу бачиш що саме, і не витрачаєш хвилини співбесіди на дебаг власних тестів.
Той самий підхід у vitest/jest
Якщо середовище дозволяє (або тебе просять «як би ти це тестував у проєкті»), синтаксис майже однаковий у vitest і jest:
import { describe, expect, it } from 'vitest';
describe('groupBy', () => {
it('групує за обчисленим ключем', () => {
const result = groupBy(['ай', 'бо', 'кіт'], (word) => word.length);
expect(result.get(2)).toEqual(['ай', 'бо']);
expect(result.get(3)).toEqual(['кіт']);
});
it('повертає порожню мапу для порожнього входу', () => {
expect(groupBy([], (x) => x).size).toBe(0);
});
it('не мутує вхідний масив', () => {
const input = ['a', 'b'];
groupBy(input, (x) => x);
expect(input).toEqual(['a', 'b']);
});
});Третій тест – «не мутує вхід» – майже ніхто не пише, а інтерв'юери його люблять: він показує розуміння побічних ефектів.
Табличні тести: багато кейсів без копіпасти
Коли кейсів більше трьох, таблиця читається краще за серію it-блоків:
it.each([
{ input: [1, 2, 2, 3], expected: [1, 2, 3] },
{ input: [], expected: [] },
{ input: [1, 1, 1], expected: [1] },
{ input: ['1', 1], expected: ['1', 1] }, // типи не змішуються
])('unique($input) -> $expected', ({ input, expected }) => {
expect(unique(input)).toEqual(expected);
});Останній рядок таблиці – хороший приклад «підступного» кейсу: рядок '1' і число 1 мають лишитися різними значеннями. Саме такі рядки в таблиці показують, що ти думаєш про типи, а не тільки про щасливий шлях.
Складніший випадок: тестуємо debounce
debounce – задача з часом, тому потрібні фейкові таймери:
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it('викликає функцію один раз після паузи', () => {
const spy = vi.fn();
const debounced = debounce(spy, 300);
debounced('a');
debounced('b');
debounced('c');
expect(spy).not.toHaveBeenCalled(); // ще чекаємо
vi.advanceTimersByTime(300);
expect(spy).toHaveBeenCalledTimes(1); // один виклик...
expect(spy).toHaveBeenLastCalledWith('c'); // ...з останнім аргументом
});Два твердження в кінці – суть debounce: не тільки «викликано один раз», а й «з останнім аргументом». Якщо на співбесіді немає vitest, ту саму ідею можна показати через setTimeout у демо-виклику і коментар, що саме ти б перевірив.
Тест до коду: полегшена версія TDD для співбесіди
Повноцінний TDD-цикл на співбесіді зазвичай не потрібен, але один прийом із нього дуже практичний: запиши перший тест до реалізації – він фіксує контракт і знімає двозначність умови.
// Умова: «згладь вкладений масив на один рівень»
// Перш ніж писати flatten, фіксуємо, що це означає:
console.assert(
JSON.stringify(flattenOnce([1, [2, 3], [4, [5]]])) ===
JSON.stringify([1, 2, 3, 4, [5]]),
'вкладеність зменшується рівно на один рівень'
);Цей assert ще до реалізації виявляє головне непорозуміння задачі: [5] лишається масивом чи ні? Якщо твоє розуміння розходиться з очікуванням інтерв'юера – ви з'ясуєте це за 30 секунд на тесті, а не за 15 хвилин на готовому коді. Фраза «давайте я зафіксую очікуваний результат прикладом» ще жодного кандидата не зробила слабшим.
Чекліст крайніх кейсів за типом задачі
Швидка шпаргалка, що перевіряти залежно від того, з чим працює функція:
| Тип входу | Обов'язкові кейси |
|---|---|
| Масив | порожній; один елемент; усі елементи однакові; дублікати |
| Рядок | порожній; один символ; пробіли; юнікод/кирилиця |
| Число | 0; від'ємне; дробове; дуже велике |
| Об'єкт | відсутнє поле; null у полі; зайві поля |
| Колбек | кидає виняток; повертає undefined; викликається 0 разів |
| Час (debounce/throttle) | кілька викликів поспіль; виклик після паузи; аргументи останнього виклику |
Не треба покривати всю таблицю на кожній задачі – обери 2-3 рядки, релевантні умові, і назви решту вголос: «за наявності часу я б ще перевірив X та Y».
Як промовляти тест-стратегію вголос
Формула з чотирьох речень, яка працює для будь-якої задачі:
- «Спочатку перевірю основний сценарій – те, заради чого функція існує.»
- «Потім порожні й мінімальні входи: порожній масив, один елемент.»
- «Далі межі контракту: дублікати, невалідні типи, від'ємні числа – залежно від умови.»
- «І окремо – відсутність побічних ефектів: вхідні дані не мутуються.»
Навіть якщо встигнеш написати лише половину цих тестів, озвучена стратегія вже зарахована.
Куди далі
Набір задач, до яких варто застосувати цей підхід, – у добірці JavaScript live coding; для фронтендерів – React-задачі з TypeScript. Як вбудувати тести в загальний план підготовки по днях – у статті про підготовку до співбесіди за 30 днів.
Практичне завдання: візьми останню розв'язану задачу і додай до неї п'ять тестів за формулою вище. Якщо хоч один упав – ти щойно знайшов баг, який знайшов би інтерв'юер.