Автор оригинала: Brandon Wozniewicz.
Я программировал в течение пяти лет и, честно говоря, я избегал тестированного развития. Я не избегал этого, потому что я не думал, что это важно. На самом деле, это казалось очень важным – а скорее потому, что мне было слишком удобно, что он не делал. Это изменилось.
Что такое тестирование?
Тестирование – это процесс обеспечения программы получает правильный ввод и генерирует правильный выход и направленные побочные эффекты. Определим эти правильные входы, выходы и побочные эффекты с Технические характеристики Отказ Возможно, вы увидели файлы тестирования с Конвенцией именования filename.spec.js
Отказ Спец
обозначает спецификацию. Это файл, где мы указываем или Assert Какой наш код должен делать, а затем проверять его, чтобы убедиться, что это делает это.
У вас есть два варианта, когда дело доходит до тестирования: ручное тестирование и автоматическое тестирование.
Ручное тестирование
Ручное тестирование – это процесс проверки вашего приложения или кода с точки зрения пользователя. Открытие браузера или программы и навигация по попытке проверить функциональность и найти ошибки.
Автоматизированное тестирование
Автоматизированное тестирование, с другой стороны, пишет код, который проверяет, работает ли другим кодом. Вопреки ручным тестированием, спецификации остаются постоянными от теста к тесту. Самое большое преимущество в состоянии проверить Многие вещи намного быстрее.
Это сочетание этих двух методов тестирования, которые выйдут как можно больше ошибок и непреднамеренных побочных эффектов, и убедитесь, что ваша программа делает то, что вы говорите, это будет. В центре внимания этой статьи находится на автоматическом тестировании, а в частности, тестирование единиц.
Рекомендация:
- Тестирование подтверждает, что наше приложение делает то, что он должен.
- Есть два типа тестов: руководство и автоматизированные
- Тесты Assert что ваша программа будет вести себя определенным образом. Тогда сам тест доказывает или опровергает это утверждение.
Тестовое развитие
Разработка для тестирования – это акт первого решения того, что вы хотите, чтобы ваша программа делать (технические характеристики), разработав неудачный тест, Тогда Написание кода, чтобы сделать этот тестовый проход. Чаще всего это связано с автоматическим тестированием. Хотя вы могли бы применить принципы к ручным тестированию.
Давайте посмотрим на простой пример: Создание деревянного стола. Традиционно мы сделаем стол, то после того, как таблица сделана, проверьте его, чтобы убедиться, что это делает, хорошо, какая таблица должна сделать. TDD, с другой стороны, у нас сначала определит, что должен сделать стол. Затем, когда это не делает эти вещи, добавьте минимальное количество «таблицы», чтобы сделать каждую работу.
Вот пример TDD для построения деревянного стола:
I expect the table to be four feet in diameter. The test fails because I have no table. I cut a circular piece of wood four feet in diameter. The test passes. __________ I expect the table to be three feet high. The test fails because it is sitting on the ground. I add one leg in the middle of the table. The test passes. __________ I expect the table to hold a 20-pound object. The test fails because when I place the object on the edge, it makes the table fall over since there is only one leg in the middle. I move the one leg to the outer edge of the table and add two more legs to create a tripod structure. The test passes.
Это будет продолжаться и включено, пока таблица не будет завершена.
Реконструировать
- С TDD, тестовая логика предшествует логике приложения.
Практический пример
Представьте, что у нас есть программа, которая управляет пользователями и их блогами. Нам нужен способ отслеживать сообщения пользователя пишет в нашей базе данных с более точностью. Прямо сейчас пользователь является объектом с именем и свойством электронной почты:
user = { name: 'John Smith', email: 'js@somePretendEmail.com' }
Мы отслеживаем сообщения пользователя создают в одном пользовательском объекте.
user = { name: 'John Smith', email: 'js@someFakeEmailServer.com' posts: [Array Of Posts] // <----- }
Каждый пост имеет название и контент. Вместо того, чтобы хранить весь пост с каждым пользователем, мы хотели бы хранить что-то уникальное, которое можно использовать для ссылки на пост. Сначала мы думали, что мы будем хранить название. Но если пользователь когда-либо меняет заголовок, или если-хотя несколько маловероятные – два названия являются точно такими же, у нас будут некоторые проблемы, ссылающиеся на этот пост блога. Вместо этого мы создадим уникальный идентификатор для каждого поста в блоге, которое мы будем хранить в Пользователь
Объект.
user = { name: 'John Smith', email: 'js@someFakeEmailServer.com' posts: [Array Of Post IDs] }
Настройте нашу среду тестирования
Для этого примера мы будем использовать шутку. Jest – это тестирование. Зачастую вам понадобится тестирование библиотеки и отдельная библиотека утверждения, но шума – это все в одном решении.
Настройка проекта
- Создать проект NPM:
NPM init
Отказ - Создать
id.js
и добавьте его в корень проекта. - Установить jest:
NPM установить jest --d
- Обновите Package.json
Тест
скрипт
// package.json { ...other package.json stuff "scripts": { "test": "jest" // this will run jest with "npm run test" } }
Это это для настройки проекта! Мы не будем иметь никакого HTML или любого стиля. Мы приближаемся к этому чисто с точки зрения единиц-тестирования. И, верьте этому или нет, у нас достаточно, чтобы бежать на самом деле прямо сейчас.
В командной строке запустите наш тестовый скрипт: NPM запустить тест
Отказ
Вы должны были получить ошибку:
No tests found In /****/ 3 files checked. testMatch: **/__tests__/**/*.js?(x),**/?(*.)+(spec|test).js?(x) - 0 matches testPathIgnorePatterns: /node_modules/ - 3 matches
Jest ищет имя файла с некоторыми конкретными характеристиками, такими как .spec
или .test
содержатся в имени файла.
Давайте обновим id.js
быть id.spec.js
Отказ
Запустите тест снова
Вы должны получить еще одну ошибку:
FAIL ./id.spec.js ● Test suite failed to run Your test suite must contain at least one test.
Немного лучше, он нашел файл, но не тест. В этом есть смысл; Это пустой файл.
Как мы пишем тест?
Испытания – это просто функции, которые получают пару аргументов. Мы можем назвать наш тест с помощью Это ()
или Тест ()
Отказ
Давайте напишем очень простой тест, просто чтобы убедиться, что работа работает.
// id.spec.js test('Jest is working', () => { expect(1).toBe(1); });
Запустите тест снова.
PASS ./id.spec.js ✓ Jest is working (3ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.254s Ran all test suites.
Мы прошли наш первый тест! Давайте проанализируем вывод теста и результатов.
Мы передаем название или описание как первый аргумент.
Тест («Whest работает»)
Второй аргумент, который мы проходим, – это функция, в которой мы на самом деле что-то отстаиваете о нашем коде. Хотя, в этом случае, мы не утверждаем что-то о нашем коде, а скорее что-то правдивое в целом, что пройдет, своего рода здравоохранение.
... () => {жду (1) .tobe (1)
});
Это утверждение математически верно, так что это простой тест, чтобы убедиться, что мы правильно проводят шума.
Результаты говорят нам, проходит ли тест или проходит неудачу. Это также сообщает нам количество испытаний и тестовых люков.
Побочная заметка о организации наших тестов
Есть другой способ организовать наш код. Мы могли бы обернуть каждый тест в Опишите
функция.
describe('First group of tests', () => { test('Jest is working', () => { expect(1).toBe(1); }); }); describe('Another group of tests', () => { // ...more tests here });
Опишите ()
Позволяет нам разделить наши тесты на разделы:
PASS ./id.spec.js First group of tests ✓ Jest is working(4ms) ✓ Some other test (1ms) Another group of tests ✓ And another test ✓ One more test (12ms) ✓ And yes, one more test
Мы не будем использовать Опишите
, но Это чаще, чем не видеть Опишите
Функция, которая включает тесты. Или даже пара описывает
-Майбе один для каждого файла, который мы тестируем. Для наших целей мы просто сосредоточимся на Тест
И держать файлы справедливо простым.
Тестирование на основе спецификаций
Как заманчиво, как это просто сесть и начать набрать набор приложений логику, хорошо сформулированный план облегчает развитие. Нам нужно определить, что сделает наша программа. Мы определяем эти цели с техническими характеристиками.
Наша спецификация высокого уровня для этого проекта состоит в том, чтобы создать уникальный идентификатор, хотя мы должны сломать это в меньшие единицы, которые мы будем тестировать. Для нашего небольшого проекта мы будем использовать следующие спецификации:
- Создать случайное число
- Номер является целым числом.
- Созданный номер находится в пределах указанного диапазона.
- Номер уникален.
Реконструировать
- Jest – это тестирование и имеет встроенную библиотеку утверждения.
- Тест – это просто функция, аргументы которых определяют тест.
- Технические характеристики определяют, что должен делать наш код и в конечном итоге, то, что мы тестируем.
Спецификация 1: создать случайное число
JavaScript имеет встроенный функцию для создания случайных чисел- Math.random ()
Отказ Наш первый тест подразделения будет смотреть, чтобы увидеть, что случайное число было создано и возвращено. Что мы хотим сделать, это использовать math.random ()
Чтобы создать номер, а затем убедитесь, что это номер, который возвращается.
Таким образом, вы можете подумать, что мы сделаем что-то вроде следующего:
Ожидайте (наши функции-вывод) .tobe (некоторое ожидаемое значение)
Отказ Проблема с нашей возвращающей ценностью является случайным, у нас нет способа узнать, чего ожидать. Нам нужно повторно назначить Math.random ()
функционировать на некоторое постоянное значение. Таким образом, когда наша функция проходит, шутка заменяет Math.random ()
с чем-то постоянным. Этот процесс называется издеваться. Итак, что мы действительно тестируем, так это то, что Math.random ()
Призывает и возвращает некоторое ожидаемое значение, которое мы можем спланировать.
Теперь, JEST также предоставляет способ доказать, что функция называется. Однако в нашем примере этот утверждение только уверяет нас Math.random ()
был назван где-то в нашем коде. Это не скажет нам, что результат Math.random ()
был также возвращаемой стоимостью.
Добавьте следующий тест на id.spec.js.
test('returns a random number', () => { const mockMath = Object.create(global.Math); mockMath.random = jest.fn(() => 0.75); global.Math = mockMath; const id = getNewId(); expect(id).toBe(0.75); });
Ломать вышеуказанный тест вниз
Во-первых, мы копируем глобальный математический объект. Тогда мы изменим Случайные
Способ вернуть постоянное значение, то, что мы можем ожидать Отказ Наконец, мы заменяем глобальную Математика
объект с нашими издевательствами Математика
объект.
Мы должны получить удостоверение личности от функции (что мы еще не создали – запомнить этот TDD). Затем мы ожидаем, что идентификатор равен 0,75-нашему издеванию возврата.
Запустите тест: NPM Run Test.
FAIL ./id.spec.js ✕ returns a random number (4ms) ● returns a random number ReferenceError: getNewId is not defined
Обратите внимание, что мы получаем ссылочную ошибку, как мы должны. Наш тест не может найти нашего getnewid ()
Отказ
Добавьте следующий код выше теста.
function getNewId() { Math.random() }
FAIL ./id.spec.js ✕ returns a random number (4ms) ● returns a random number expect(received).toBe(expected) // Object.is equality Expected: 0.75 Received: undefined
Мы снова потерпели неудачу с тем, что называется Ошибка утверждения Отказ Наша первая ошибка была справочной ошибкой. Эта вторая ошибка говорит нам, что она получила undefined
Отказ Но мы позвонили Math.random ()
так что случилось? ПОМНИТЕ, ФУНКЦИИ, которые явно не возвращают что-то, что неясно вернутся undefined
Отказ Эта ошибка – хорошая подсказка, что что-то не было определено, например, переменной, или, как и в нашем случае, наша функция ничего не возвращает.
Обновите код до следующего:
function getNewId() { return Math.random() }
Запустить тест
PASS ./id.spec.js ✓ returns a random number (1ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total
Поздравляю! Мы прошли наш первый тест.
Спецификация 2: Номер, который мы возвращаем, это целое число.
Math.random ()
генерирует число от 0 до 1 (не включено). Код, который у нас есть, никогда не генерируют такое целое число. Это нормально, это TDD. Мы проверим целое число, а затем напишите логику, чтобы преобразовать наш номер в целое число.
Итак, как мы проверяем, является ли номер целым числом? У нас есть несколько вариантов. Напомним, мы издевались Math.random ()
Выше, и мы возвращаем постоянное значение. На самом деле, мы создаем реальную ценность, так как мы возвращаем число от 0 до 1 (не включено). Например, если мы возвращаем строку, мы не могли получить этот тест для прохождения. Или, если с другой стороны, мы возвращаем целое число для нашего издевательства, тест всегда (ложно) проходит.
Таким образом, ключевой вынос – если вы собираетесь использовать издеватые значения возврата, они должны быть реалистичными, поэтому наши тесты возвращают значимую информацию с этими значениями.
Другой вариант будет использовать Номер .isinteger ()
, передавая наш идентификатор в качестве аргумента и видение, если это верно.
Наконец, без использования издеватых значений мы можем сравнить ID, мы вернемся к своей целой версии.
Давайте посмотрим на вариант 2 и 3.
Вариант 2: Использование номера .isintheger ()
test('returns an integer', () => { const id = getRandomId(); expect(Number.isInteger(id)).toBe(true); });
Тест не удается, как следствие.
FAIL ./id.spec.js ✓ returns a random number (1ms) ✕ returns an integer (3ms) ● returns an integer expect(received).toBe(expected) // Object.is equality Expected: true Received: false
Тест терпит неудачу с Ошибка Boolean Assertion Отказ Напомним, есть несколько способов провалиться тест. Мы хотим, чтобы они потерпели неудачу с ошибками утверждений. Другими словами, наше утверждение не то, что мы говорим, это. Но тем более, мы хотим, чтобы наш тест терпеть неудачу с Ошибки утверждения стоимости Отказ
Ошибки болеобразных утверждений (True/False Arrors) Не дают нам очень много информации, но ошибка подтверждения стоимости делает.
Вернемся в наш деревянный стол. Теперь нести со мной, следующие два утверждения могут показаться неловкими и трудными для чтения, но они здесь, чтобы выделить точку:
Во-первых, вы можете утверждать, что Стол синяя [будет] правда Отказ В другом утверждении вы можете утверждать Цвет таблицы [быть] синий Отказ Я знаю, что это неловко сказать и может даже выглядеть как одинаковые утверждения, но они не. Взгляните на это:
Ожидайте (Table.isblue) .tobe (true)
против
Ожидайте (Table.Color) .tobe (синий)
Предполагая, что таблица не синяя, ошибка первых примеров скажет нам, что она ожидала правдой, но получила ложь. Вы понятия не имеете, какого цвета столик. Мы очень хорошо, возможно, забыли рисовать это вообще. Вторые примеры ошибки, однако, могут сказать нам, что она ожидала синего, но получила красную. Вторым примером гораздо более информативно. Это указывает на корень проблемы гораздо быстрее.
Давайте перепишем тест, используя опцию 2, вместо этого принять ошибку подтверждения значения.
test('returns an integer', () => { const id = getRandomId(); expect(id).toBe(Math.floor(id)); });
Мы говорим, что мы ожидаем, что удостоверение личности мы получим от нашей функции, чтобы быть равными полам этого удостоверения личности. Другими словами, если мы получаем целое число назад, то пол этого целого числа равен цельному числу.
FAIL ./id.spec.js ✓ returns a random number (1ms) ✕ returns an integer (4ms) ● returns an integer expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 0.75
Вау, каковы шансы этой функции просто произошло, чтобы вернуть издевательствоемое значение! Ну, они на 100% на самом деле. Несмотря на то, что наше насмемодомое значение, кажется, считается только первым тестом, мы фактически переназначиваем глобальную ценность. Так что независимо от того, насколько вложен, что происходит повторное назначение, мы меняем глобальный Математика
объект.
Если мы хотим что-то изменить перед каждым тестом, есть лучшее место, чтобы поставить его. Jest предлагает нам Rebedeach ()
метод. Мы проходим в функции, которая запускает любой код, который мы хотим запустить перед каждым из наших тестов. Например:
beforeEach(() => { someVariable = someNewValue; }); test(...)
Для наших целей мы не будем использовать это. Но давайте немного изменим наш код, чтобы мы сбросили глобальные Математика
объект обратно к по умолчанию. Вернитесь в первый тест и обновите код следующим образом:
test('returns a random number', () => { const originalMath = Object.create(global.Math); const mockMath = Object.create(global.Math); mockMath.random = () => 0.75; global.Math = mockMath; const id = getNewId(); expect(id).toBe(0.75); global.Math = originalMath; });
Что мы здесь делаем, сохраняют по умолчанию Математика
Объект, прежде чем мы перезаписываем любой из этого, затем переназнайте его после завершения нашего теста.
Давайте снова запустим наши тесты, специально фокусируясь на нашем втором тесте.
✓ returns a random number (1ms) ✕ returns an integer (3ms) ● returns an integer expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 0.9080890805713182
Поскольку мы обновили наш первый тест, чтобы вернуться к по умолчанию Математика
Объект, мы действительно получаем случайное число сейчас. И как и тест раньше, мы ожидаем получить целое число или, другими словами, пол сгенерированного номера.
Обновите нашу логику приложения.
function getRandomId() { return Math.floor(Math.random()); // convert to integer } FAIL ./id.spec.js ✕ returns a random number (5ms) ✓ returns an integer ● returns a random number expect(received).toBe(expected) // Object.is equality Expected: 0.75 Received: 0
Э-э, наш первый тест не удался. Так что случилось?
Ну, потому что мы издеваем нашу возвращенную ценность. Наш первый тест возвращает 0,75, независимо от того, что. Мы ожидаем, однако, чтобы получить 0 (пол 0,75). Может быть, было бы лучше проверить, если Math.random ()
позывается. Хотя, это несколько бессмысленно, потому что мы могли бы позвонить Math.random ()
В любом месте нашего кода никогда не используйте его, а тест все еще проходит. Может быть, мы должны проверить, возвращает ли наша функция номер. Ведь наш идентификатор должен быть числом. Еще раз, мы уже тестируем, если мы получаем целое число. И все целые числа – это цифры; этот тест будет избыточным. Но есть еще один тест, который мы могли бы попробовать.
Когда все это сказано и сделано, мы ожидаем получить целое число обратно. Мы знаем, что мы будем использовать Math.floor ()
сделать это. Так что, возможно, мы можем проверить, если Math.floor ()
называется Math.random ()
как аргумент.
test('returns a random number', () => { jest.spyOn(Math, 'floor'); // <--------------------changed const mockMath = Object.create(global.Math); const globalMath = Object.create(global.Math); mockMath.random = () => 0.75; global.Math = mockMath; const id = getNewId(); getNewId(); //<------------------------------------changed expect(Math.floor).toHaveBeenCalledWith(0.75); //<-changed global.Math = globalMath; });
Я прокомментировал линии, которые мы изменились. Во-первых, обратите внимание на конец фрагмента. Мы утверждаем, что была вызвана функция. Теперь вернитесь к первому изменению: jest.spyon ()
Отказ Для того, чтобы наблюдать, если функция была вызвана, Jest требует, чтобы мы либо издевались на эту функцию, либо шпионить на нем. Мы уже видели, как издеваться над функцией, так что здесь мы шпионь на Math.floor ()
Отказ Наконец, другие изменения, которые мы сделали, было просто позвонить getnewid ()
не назначая его возвращаемого значения для переменной. Мы не используем удостоверение личности, мы просто утверждаем, что он вызывает некоторую функцию с некоторым аргументом.
Запустить наши тесты
PASS ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total
Поздравляю со вторым успешным тестом.
Спецификация 3: число находится в пределах указанного диапазона.
Мы знаем Math.random ()
Возвращает случайное число от 0 до 1 (не включено). Если разработчик хочет вернуть номер между 3 и 10, что она могла сделать?
Вот ответ:
Math.floor (math.random () * (max – min + 1))) + мин;
Приведенный выше код будет производить случайное число в диапазоне. Давайте посмотрим на два примера, чтобы показать, как это работает. Я смоделирую два произвольных количества, создаваемых, а затем примените оставшуюся часть формулы.
Пример: Число от 3 до 10. Наши случайные числа будут .001 и .999. Я выбрал экстремальные значения в качестве случайных чисел, чтобы вы могли видеть окончательный результат оставаться в пределах диапазона.
0,001 * (10-3 + 1) +.008
Пол это 3.
0,999 * (10-3 + 1) +.992
Пол это 10.
Давайте напишем тест
test('generates a number within a specified range', () => { const id = getRandomId(10, 100); expect(id).toBeLessThanOrEqual(100); expect(id).toBeGreaterThanOrEqual(10); }); FAIL ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer (1ms) ✕ generates a number within a specified range (19ms) ● generates a number within a specified range expect(received).toBeGreaterThanOrEqual(expected) Expected: 10 Received: 0
Пол Math.random ()
Всегда будет 0, пока мы не обновим наш код. Обновите код.
function getRandomId(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } FAIL ./id.spec.js ✕ returns a random number (5ms) ✓ returns an integer (1ms) ✓ generates a number within a specified range (1ms) ● returns a random number expect(jest.fn()).toHaveBeenCalledWith(expected) Expected mock function to have been called with: 0.75 as argument 1, but it was called with NaN.
О нет, наш первый тест снова не удался! Что случилось?
Просто, наш тест утверждает, что мы называем Math.floor ()
с 0,75
Отказ Тем не менее, мы фактически называем его 0,75 плюс и минус максимальное и минимальное значение, которое еще не определено. Здесь мы перепишем первый тест, чтобы включить некоторые из наших новых знаний.
test('returns a random number', () => { jest.spyOn(Math, 'floor'); const mockMath = Object.create(global.Math); const originalMath = Object.create(global.Math); mockMath.random = () => 0.75; global.Math = mockMath; const id = getNewId(10, 100); expect(id).toBe(78); global.Math = originalMath; }); PASS ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer ✓ generates a number within a specified range (1ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total
Мы сделали некоторые довольно большие изменения. Мы передали некоторые выборки в нашу функцию (10 и 100 как минимальные и максимальные значения), и мы снова изменили наше утверждение, чтобы проверить определенное значение возврата. Мы можем сделать это, потому что мы знаем, если Math.random ()
Вызывается, значение установлено значение 0,75. И, когда мы применяем наши минимальные и максимальные расчеты в 0,75
Мы получим тот же номер каждый раз, что в нашем случае составляет 78.
Теперь мы должны начать удивляться, если это даже хороший тест. Мы должны были вернуться и прессмировать наш тест, чтобы соответствовать нашему коду. Это немного поступает против духа TDD. TDD говорит, что изменил ваш код, чтобы пройти тест, чтобы не изменить тест, чтобы пройти тест. Если вы узнаете, что пытаетесь исправить тесты, поэтому они проходят, это может быть признаком плохого теста. Тем не менее, я хотел бы оставить тест здесь, так как есть пара хороших концепций. Тем не менее, я призываю вас рассмотреть эффективность такого теста, как это, а также лучший способ написать его, или если даже важно включить вообще.
Вернемся к нашему третьему тесту, который генерировал номер в пределах диапазона.
Мы видим, что это прошло, но у нас проблема. Можете ли вы думать об этом?
Вопрос, мне интересно, это ли нам счастливчики? Мы сгенерировали только одно случайное число. Каковы шансы, что число только что оказалось в диапазоне и пройти тест?
К счастью, здесь мы можем математически доказать наш код работы. Тем не менее, для удовольствия (если вы сможете называть это удовольствием), мы будем обернуть наш код в для петли
который проходит 100 раз.
test('generates a number within a defined range', () => { for (let i = 0; i < 100; i ++) { const id = getRandomId(10, 100); expect(id).toBeLessThanOrEqual(100); expect(id).toBeGreaterThanOrEqual(10); expect(id).not.toBeLessThan(10); expect(id).not.toBeGreaterThan(100); } });
Я добавил несколько новых утверждений. Я использую .not
только чтобы продемонстрировать другие доступешевые API.
PASS ./id.spec.js ✓ is working (2ms) ✓ Math.random() is called within the function (3ms) ✓ receives an integer from our function (1ms) ✓ generates a number within a defined range (24ms) Test Suites: 1 passed, 1 total Tests: 4 passed, 4 total Snapshots: 0 total Time: 1.806s
С 100 итерациями мы можем чувствовать себя довольно уверенно, наш код сохраняет наш идентификатор в пределах указанного диапазона. Вы также можете преднамеренно попробовать провалить тест для добавления подтверждения. Например, вы можете изменить одну из утверждений на не Ожидайте значение более 50, но все еще проходят в 100 в качестве максимального аргумента.
Это нормально использовать несколько утверждений в одном тесте?
Да. Это не значит, что вы не должны пытаться уменьшить эти множественные утверждения к одному утверждению, которое более надежно. Например, мы могли бы переписать наш тест, чтобы быть более надежным и уменьшить наши утверждения только к одному.
test('generates a number within a defined range', () => { const min = 10; const max = 100; const range = []; for (let i = min; i < max+1; i ++) { range.push(i); } for (let i = 0; i < 100; i ++) { const id = getRandomId(min, max); expect(range).toContain(id); } });
Здесь мы создали массив, который содержит все номера в нашем диапазоне. Затем мы проверяем, находится ли идентификатор в массиве.
Спецификация 4: номер уникален
Как мы можем проверить, является ли номер уникальным? Во-первых, нам нужно определить, что означает уникальный для нас. Скорее всего, где-то в нашем приложении мы бы имели доступ ко всем его идентификаторам уже. Наш тест должен утверждать, что номер, который генерируется, не в списке текущих идентификаторов. Есть несколько разных способов решения этого. Мы могли бы использовать .not.tocontain ()
Мы видели раньше, или мы могли бы использовать что-то с индекс
Отказ
индекс чего-либо()
test('generates a unique number', () => { const id = getRandomId(); const index = currentIds.indexOf(id); expect(index).toBe(-1); });
array.indexof ()
Возвращает положение в массиве элемента, в которой вы проходите. Он возвращает -1
Если массив не содержит элемент.
FAIL ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer ✓ generates a number within a defined range (25ms) ✕ generates a unique number (10ms) ● generates a unique number ReferenceError: currentIds is not defined
Тест не удается со ссылкой на ошибку. curecieds
не определен. Давайте добавим массив для симуляции некоторых идентификаторов, которые могут уже существовать.
const currentIds = [1, 3, 2, 4];
Повторно запустите тест.
PASS ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer ✓ generates a number within a defined range (27ms) ✓ generates a unique number Test Suites: 1 passed, 1 total Tests: 4 passed, 4 total
В то время как тест проходит, это должно еще раз поднять красный флаг. У нас есть абсолютно Ничего Это обеспечивает уникальное число. Так что случилось?
Опять же, нам повезет. На самом деле, Ваш Тест может потерпеть неудачу. Хотя, если вы запустите его снова и снова, вы, скорее всего, получите смешу обоих с гораздо больше, чем сбои из-за размера curecieds
Отказ
Одна вещь, которую мы могли бы попробовать, это обернуть это в для петли
Отказ Достаточно большой для петли
Скорее всего, заставит нас потерпеть неудачу, хотя возможно, что они все проходят. То, что мы могли бы сделать, это проверить, чтобы увидеть, что наше getnewid ()
Функция может каким-то образом быть осознанным, когда число является или не является уникальным.
Например. Мы могли бы установить curecieds = [1, 2, 3, 4, 5]
Отказ Тогда позвоните Гетрандомид (1, 5)
Отказ Наша функция должна понимать, что нет значения, оно не может генерировать из-за ограничений и передать свое сообщение об ошибке. Мы могли бы проверить это сообщение об ошибке.
test('generates a unique number', () => { mockIds = [1, 2, 3, 4, 5]; let id = getRandomId(1, 5, mockIds); expect(id).toBe('failed'); id = getRandomId(1, 6, mockIds); expect(id).toBe(6); });
Есть несколько вещей, чтобы заметить. Есть два утверждения. В первом утверждении мы ожидаем, что наша функция не удается с тех пор, как мы ограничиваем ее таким образом, чтобы она не должна вернуть любое число. Во втором примере мы ограничиваем его таким образом, где он должен быть только в состоянии вернуть 6
Отказ
FAIL ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer (1ms) ✓ generates a number within a defined range (24ms) ✕ generates a unique number (6ms) ● generates a unique number expect(received).toBe(expected) // Object.is equality Expected: "failed" Received: 1
Наш тест терпит неудачу. Поскольку наш код не проверяет ничего или возвращается не удалось
, это ожидается. Хотя, возможно, ваш код получил от 2 до 6.
Как мы можем проверить, если наша функция не может Найти уникальный номер?
Во-первых, нам нужно сделать какой-то цикл, который продолжит создавать номера, пока не найдет тот, который действителен. В какой-то момент, если нет действительных чисел, нам нужно выйти из цикла, поэтому мы избегаем бесконечной ситуации петли.
Что мы сделаем, это отслеживать каждый номер, который мы создали, и когда мы создали каждое число, которое мы можем, и ни один из этих чисел не передает нашу уникальную проверку, мы выберем из цикла и предоставим некоторые отзывы.
function getNewId(min = 0, max = 100, ids =[]) { let id; do { id = Math.floor(Math.random() * (max - min + 1)) + min; } while (ids.indexOf(id) > -1); return id; }
Во-первых, мы изменили getnewid ()
Чтобы включить параметр, который представляет собой список текущих идентификаторов. Кроме того, мы обновили наши параметры, чтобы обеспечить значения по умолчанию в случае, если они не указаны.
Во-вторых, мы используем делать - пока
Цикл, так как мы не знаем, сколько раз потребуется, чтобы создать случайное число, которое является уникальным. Например, мы могли бы указать номер от 1 до 1000 с Только Номер недоступно 7. Другими словами, наш текущий идентификатор имеет только один 7 в нем. Хотя наша функция имеет 999 других номеров на выбор, оно может теоретически вызвать номер 7 снова и снова и снова. Хотя это очень маловероятно, мы используем делать - пока
Петля, так как мы не уверены, сколько раз он будет работать.
Кроме того, обратите внимание, мы вырвались из цикла, когда наш идентификатор это уникальный. Мы определяем это с indexof ()
Отказ
У нас все еще есть проблема, с кодом в настоящее время то, как оно есть, если нет доступных номеров, цикл продолжит работать, и мы будем в бесконечном цикле. Нам нужно отслеживать все номера, которые мы создаем, поэтому мы знаем, когда у нас нет номеров.
function getRandomId(min = 0, max = 0, ids =[]) { let id; let a = []; do { id = Math.floor(Math.random() * (max - min + 1)) + min; if (a.indexOf(id) === -1) { a.push(id); } if (a.length === max - min + 1) { if (ids.indexOf(id) > -1) { return 'failed'; } } } while (ids.indexOf(id) > -1); return id; }
Вот что мы сделали. Мы решаем эту проблему, создав массив. И каждый раз, когда мы создаем номер, добавьте его в массив (если только это уже там). Мы знаем, что мы пробовали каждое число хотя бы один раз, когда длина этого массива равна диапазоне, которое мы выбрали плюс один. Если мы доберемся до этого момента, мы создали последний номер. Однако мы все еще хотим убедиться, что последний номер, который мы создали, не передает уникальный тест. Потому что, если это делает, хотя мы хотим закончить цикл, мы все еще хотим вернуть этот номер. Если нет, мы вернемся “не удалось”.
PASS ./id.spec.js ✓ returns a random number (1ms) ✓ returns an integer (1ms) ✓ generates a number within a defined range (24ms) ✓ generates a unique number (1ms) Test Suites: 1 passed, 1 total Tests: 4 passed, 4 total
Поздравляем, мы можем отправить наш идентификационный генератор и сделать наши миллионы!
Заключение
Некоторые из того, что мы сделали, было для демонстрационных целей. Тестирование того, было ли наш номер в пределах указанного диапазона, это весело, но эта формула может быть математически доказана. Таким образом, лучшее тест может быть, чтобы убедиться, что формула называется.
Кроме того, вы можете получить более креативно с генератором случайного идентификатора. Например, если он не может найти уникальный номер, функция может автоматически увеличить диапазон на одну.
Еще одна вещь, которую мы видели, так это то, как наши тесты и даже спецификации могут немного критить, как мы тестируем и рефакторируем. Другими словами, это было бы глупо думать, что ничто не изменится на протяжении всего процесса.
В конечном итоге, развитие, ориентированное на тестирование, дает нам рамки, чтобы подумать о нашем коде на более гранулированном уровне. Это зависит от вас, разработчик, чтобы определить, как гранунтирован вы должны определить ваши тесты и утверждения. Имейте в виду, тем больше у вас есть тесты, и чем более узко сосредоточены ваши тесты, тем более плотно связаны, они становятся с вашим кодом. Это может привести к нежеланию рефактору, потому что теперь вы также должны обновлять свои тесты. Конечно, есть баланс в количестве и детализации ваших тестов. Баланс зависит от вас, разработчик, чтобы выяснить.
Спасибо за прочтение!
ворота