| title | Занятие 4 |
|---|---|
| description | Тестирование кода как часть процесса разработки, пример применения тестирования к домашним заданиям |
Есть разные определения тестирования:
- выполнение программы с целью нахождения ошибок
- практика для получения надежного ПО без излишних усилий на его проверку
- проверка соответствия между реальным поведением программы и её ожидаемым поведением
- процесс наблюдения за работой программы в специальных условиях и выводы на основе наблюдения
- ...
Виды тестирования по степени автоматизации:
- автоматизированное
- ручное
А вообще тестирование может быть разным:
Есть даже
ad-hoc testing — вид тестирования, который выполняется без подготовки к тестам, без определения ожидаемых результатов, проектирования тестовых сценариев. Это неформальное, импровизационное тестирование.
Но! Это не наш метод.
С самого начала у нас есть какая то тактика и мы ее придерживаемся
Почему автоматизированное тестирование?
- скорость проверки
- простота проверки
- исключение человека из процесса (страдают машины)
- решение проблемы регрессионного тестирования
- упрощает тестирование отдельных частей и сопряжений
Цели автоматического тестирования с точки зрения разработчика:
- Проверка соответствия ожиданиям
- Документирование кода (Примеры использования)
- Проверка состояния системы (регрессия)
- Поиск / проверка на ошибки
Что тестируют js-разработчики
- Функционал
- Кроссбраузерность
- Нагрузочное тестирование
- Стресс тестирование (подвид нагрузочного тестирования, направленный на исследование пределов работоспособности системы)
- Юзабилити
- Безопасность
Относительно выполнения кода тестирование кода может быть:
- Без выполнения кода (статический анализ: Eslint, Typescript, JSDoc)
- С выполнением кода (полным или частичным)
Модульные (unit) и интеграционные (service) тесты:
- Просты в написании
- Тестируют реализацию
- Имеют высокую скорость и точность выявления проблем
- Код для таких тестов "Белый"/"серый" ящик
- Влияют на устройство кода "внутри"
Но при этом их не достаточно:
И они не покрывают интеграцию частей
Общая схема тестирования:
- подготовка окружения и данных
- выполнение кода
- проверка утверждения
const numbersList = [1, 2, 3]; // подготовка
const indexOf5 = numbersList.indexOf(5); // выполнение
expect(indexOf5).toBe(-1); // проверка// сокращенно
expect([1, 2, 3].indexOf(5)).toBe(-1);Не обязательно использовать специальные инструменты
console.log("111", isPalindrom("111"), true);
console.log("112", isPalindrom("112"), false);
console.log("212", isPalindrom("212"), true);
console.log("2112", isPalindrom("2112"), true);[
["111", true],
["112", false],
["212", true],
["2112", true],
].forEach(([str, expectedResult]) =>
console.log(str, isPalindrom(str) === expectedResult)
);И как эволюция таких решения появились тестовые фреймворки. Которые должны упростить работу с типовыми операциями при тестировании.
Что делают тестовые фреймворки:
- задает формат тестов
- объединяет библиотеки, необходимые для тестирования
- запускает тесты
- выводят результат
- позволяет работать с данными для запуска тестов
Для Javascript их много (как и для других платформ)
Все похожи и каждый особенный
На курсе мы будем работать с Jest
- Широко используется (Facebook, Uber, AirBnb, etc)
- Поддерживает из коробки много технологий (React, Vue, Typescript)
- Содержит дополнительные инструменты (так что их не нужно дополнительно настраивать)
- Производительность
- Дружит с другими инструментами
Особенности Jest:
- не работает в браузере (только через Node.js)
- может использовать для запуска тестов jsdom - это похоже на браузер, но не то
- позволяет делать подмену модулей
➜ js--game-of-life git:(master) npx jest
PASS src/getNewCellState.test.js
PASS src/getCellState.test.js
PASS src/isAnyoneAlive.test.js
PASS src/getNumOfAliveNeighbours.test.js
PASS src/getNextState.test.js
PASS src/drawField.test.js
PASS src/createGameOfLife.test.js
Test Suites: 7 passed, 7 total
Tests: 43 passed, 43 total
Snapshots: 0 total
Time: 3.79 s
Ran all test suites.➜ js--game-of-life git:(master) npx jest --verbose
PASS src/isAnyoneAlive.test.js
isAnyoneAlive
✓ is a function (1 ms)
✓ returns `false` for empty field
✓ returns `true` for field 1x1 from 1 (1 ms)
✓ should return false for []
✓ should return false for [[]]
✓ should return true for [[1]]
✓ should return true for [[1],[0]]
✓ should return false for [[0],[0]] (1 ms)
✓ should return true for [[0,0,0],[0,0,1]]
Test Suites: 1 passed, 1 total
Tests: 9 passed, 9 total
Snapshots: 0 total
Time: 1.274 s
Ran all test suites.Тесты обычно помещаются в файлы с окончанием .test / .spec.
Иногда их складывают в отдельные директории, но на курсе мы будем держать тесты рядом с кодом.
Тесты группируются в описания (блоки describe), описания могут быть вложенными.
describe("Module name", () => {});
describe("Another module name", () => {
describe("Nested module", () => {});
});Описание включает в себя один или несколько тестов-утверждений (it).
describe("isPalindrom", () => {
it("is a function", () => {});
it('returns true for "121"', () => {});
});Внутри теста находится одно или несколько ожиданий (на последней версии jest см сюда)
describe("isPalindrom", () => {
it('returns true for "121"', () => {
expect(isPalindrom("121")).toBe(true);
});
});Возможностей писать ожидания (v30 doc) много, но свести все можно к проверке утверждения на истинность. Просто использование разных ожиданий может упростить жизнь.
function sumWithCallback(a, b, cb) {
cb(a + b);
}
//...
it("calls cb with result", () => {
let isCalled = false;
let param = undefined;
const cb = (x) => {
isCalled = true;
param = x;
};
sumWithCallback(1, 2, cb);
expect(isCalled).toBeTruthy();
expect(param === 3).toBeTruthy();
});function sumWithCallback(a, b, cb) {
cb(a + b);
}
//...
it("calls cb with result", () => {
const cb = jest.fn();
sumWithCallback(1, 2, cb);
expect(cb).toHaveBeenCalledWith(3);
});Если в тестах есть повторяющиеся операции по подготовке окружения - их можно выносить в специальные хуки
beforeAllbeforeEachafterEachafterAll
Чем лучше вы знаете свой тестовый фреймворк - тем проще и быстрее вам писать тесты.
Автоматизированные тесты проверяют один или несколько модулей в разных условиях. Значит для этого должна быть возможность запускать код многократно.
Что это для нас означает на уровне кода?
Что нужно тестировать:
- нормальную работу
- граничные значения
- обработку исключительных ситуаций
Для данной строки нужно проверить, что она содержит одинаковое число x и o. Регистр не имеет значения. Если число вхождений одинаковое - вернуть true, иначе false
Какие тестовые сценарии будем тестировать?
В файле isEqualXAndO.test.js описать перечисленные сценарии (не меньше 5).
Время выполнения - 5 минут
- Определите модули, их интерфейсы и возможно структуру
- Определите где и как модули будут сопрягаться
- Начинайте с тестирования частей, чтобы быстро получать обратную связь
- Держите время на тест коротким (опять же для быстрой обратной связи)
- Держите тесты чистыми
- Тесты не должны зависеть друг от друга
- Используйте
--watchрежим, для автоматического перезапуска тестов
- Пишите чистые функции (которые зависят только от входных переменных, всегда выдают предсказуемый результат и не меняют ничего в окружении) (а такое вообще возможно?)
- Пишите функции, которые принимают зависимости через параметры (не полагайтесь на замыкания и глобальные объекты)
- Сначала пишите тесты, а потом код
- Помните, что тесты тоже код. И тут действуют те же правила - KISS, DRY и тп.
TDD и цикл RGR
- R1: красный тест (проверяется функциональность, которой нет)
- G: зеленый тест (пишется функциональность, необходимая для теста)
- повторять до получения результата
- R2: рефакторинг (тесты уже есть, вы можете менять реализацию)
Задача - сделать поведение системы предсказуемым (и управляемым), в тех частях, которые мы не хотим тестировать, или которые не являются предсказуемыми в реальной жизни.
В рамках Jest есть такие инструменты (самые частые)
jest.fn()- для создания новых mock-функцийjest.spyOn()- для оборачивания в mock-функции существующие методыjest.mock- для мока модулей (файлов)
it("callFn alerts value entered to prompt", () => {
jest.spyOn(window, "prompt").returnValue("123");
window.alert = jest.fn();
expect(window.alert).toHaveBeenCalledWith("123");
});Держите тесты чистыми
let originalAlert;
beforeEach(() => {
originalAlert = window.alert;
});
afterEach(() => {
window.alert = originalAlert;
});
it("callFn alerts value entered to prompt", () => {
jest.spyOn(window, "prompt").returnValue("123");
expect(window.alert).toHaveBeenCalledWith("123");
});Почему не jest.spyon(window, 'alert')
Задача: Выбор случайного значения
-
Вывести на экран элемент
textarea, который позволяет ввести многострочный текст, и кнопку. -
По нажатию на кнопку нужно вывести
alertслучайную строку изtextarea. При этом пустые и пробельные строки игнорируются. Если поле ввода пустое - показать сообщение "Please provide at least 1 option".
Какие тесты нужны для написания программы?
- YT: Разработка через тестирование в JS или как начать любить программирование
- Jest - Начало работы (Весь блок
Introductionв документации) - Мои «Ого, я этого не знал!» моменты с Jest
- YT: Jest. Unit Тестирование в JavaScript
- JavaScript & Node.js testing best practices
- Автоматизированное тестирование (в контексте разных языков, обзорно)
- YT: Важные нюансы тестирования которые редко рассказывают (с примером на typescript + jest)
-
Из каких шагов состоит автоматизированный тест?
-
Самые популярные 5 тестовых фреймворков для Javascript?
-
Для чего нужны моки и стабы?
-
Что такое spy-функции?
-
Что такое пирамида тестирования?
-
Что лучше - e2e или unit тест?
-
Что такое "чистая" функция?
-
Что такое "внедрение зависимостей"?
-
Как запустить jest в режиме наблюдения?
-
Какие хуки для тестовых наборов поддерживаются jest?
-
Какие есть формы написания асинхронных тестов в jest?
-
Что такое jsdom?
-
Чем
describeотличается отit?
Опрос о занятии


