Тесты и автоматическая проверка кода
В видео есть неточность. Функция факториала, показанная в видео, использует цикл, а не рекурсию
Транскрипт урока
Вот вы программируете, пишете код, функции и все такое. Вы хотите, чтобы все это работало, конечно, поэтому проверяете все ли в порядке с кодом, нет ли в нем ошибок и он делает то, для чего вы его писали. Вы не слишком беспокоитесь о синтаксических ошибках или ошибках типизации, поскольку вы обнаруживаете их сразу же, как только запускаете свои программы.

Но есть один тип подлых ошибок, с которыми все сложнее, и компьютеры не умеют находить их без нашей помощи. Это логические ошибки.

Предположим, вы написали функцию square. Она принимает число и возвращает его квадрат. Даете ей 2, она должна вернуть 4. Даете 6 — она должна вернуть 36.

Если вы сделали ошибку, и эта функция возвращает неверное значение, компьютер или интерпретатор языка программирования будут не в состоянии распознать это как ошибку. Они делают только то, что вы сказали им делать, поэтому если вы укажете, что 2 в квадрате должно быть 11, правильным ответом станет 11 — вот и вся магия.

Один очевидный способ удостовериться, что ваша функция выполняет то, чего вы от нее хотите, это запустить ее и посмотреть.

Мы можем запустить console.log(square(2)); и если она выведет 4, то все в порядке. Ну, может быть в порядке не совсем все… чтобы не сомневаться, давайте проверим square(6). Результат -- 36, значит все в порядке. Ну, еще одну, ладно: 100 в квадрате. Десять тысяч — хорошо.

На данном этапе вы уже можете быть достаточно уверенными, что функция square действительно возводит величину в квадрат. Эта функция простая и вам не нужно сидеть часами и тестировать ее. Но не все ваши функции такие же простые. Даже функция факториала из одного из предыдущих уроков намного-намного сложнее.

Да, и знаете что еще? 2-х, 4-х и 100-а не достаточно. Вам еще нужно проверить отрицательные числа, верно? -2 возведенное в квадрат должно быть 4, а не -4, а мы этого не проверили. О… а 0 в квадрате. А, подождите! Еще кое что: мы должны проверить, что эта квадратная функция не сломается, если вы передадите в нее что-то совсем не то, например строчку. Она должна, вероятно, вернуть, что "собака" в квадрате — это не число.

Писать все эти штуки и выводить на экран вручную как-то странно, особенно учитывая то, что у нас есть эта мегамощная машина, которая делает все, что мы прикажем. Так что, давайте прикажем ей тестировать для нас функции. Мы знаем, что эти функции ДОЛЖНЫ возвращать. Поэтому компьютер может проверить, действительно ли они возвращают то, что надо.

Представьте себе функцию assert, которая работает с тремя элементами:
  • функцией, которую мы хотим протестировать
  • аргументом, который принимается этой функцией
  • значением, которое мы СЧИТАЕМ должна вернуть эта функция
assert скажет нам, действительно ли функция возвращает значение, которое мы ожидаем.
Давайте вызовем assert, чтобы увидеть, действительно ли функция square возвращает 4, когда вызывается с аргументом 2:

assert(square(2) === 4);
Если функция square верна в случае с 2, то она возвратит 4, а раз 4 равно 4, assert не пожалуется. Мы говорим — этот тест пройден.

Другой сценарий: 6 в квадрате определенно не должно быть 4. Поэтому:

assert(square(6) !== 4);
Этот тест пройдет, если функция square возвратит любое значение, кроме 4.
Что происходит, когда рассматриваемая функция не работает как нужно?

Например, ее результат показывает, что 2 в квадрате — это 6, а не 4. Тогда первый тест провалится, и мы увидим ошибку, типа такой:

Assertion error: false == true
  at square (/home/rakhim/code/planets.js:4:3)
  at surfaceArea (/home/rakhim/code/planets.js:8:10)
Она выглядит очень похожей на синтаксическую ошибку, и благодаря этой магической функции assert, мы видим новый тип ошибки — ошибку утверждения. Это значит какой-то тест не прошел, провалился, поэтому нам нужно починить код.

Тут он говорит Assertion error: false == true. Это потому что тест ожидал, что выражение будет истинным, но оно было ложным. square(2) === 4 должно быть истинным, но раз наша поломанная функция возведения в квадрат возвращает 6, выражение становится ложным. Ложное это не истинное, следовательно — ошибка.

Снизу виден стектрейс, он показывает вам, где найти проваленный тест — название файла, номер строчки в этом файле и позицию в этой строчке.

Есть другие функции assert для более удобного и легкочитаемого вывода. Вот одна из них:

assert.equal(square(2), 4);
assert.equal — это специальная функция для тестирования равенства, она принимает два аргумента: то, что вы хотите протестировать и значение, которое вы ожидаете. Пройденный тест просто пройдет, а вот проваленный даст вам какую-то информацию:

Assertion error: 6 == 4
  at square (/home/rakhim/code/planets.js:4:3)
  at surfaceArea (/home/rakhim/code/planets.js:8:10)
Вы можете увидеть, какой был фактический результат, а какой ожидаемый, и, в частности, где они не совпали.

Эта функция assert не просто что-то, что я нафантазировал, это библиотека тестирования для JavaScript программ. Библиотека — это набор полезных функций, код других людей, который вы можете добавить в свою программу и упростить себе жизнь.

В JavaScript существуют другие библиотеки для тестирования кода, и в других языках есть свои. Тестирование — очень важная задача, и часто крупный блок всего кода компании — это только тесты для этого самого кода. Например, этот сайт — hexlet.io, состоит из десятков тысяч строчек кода и треть их — тесты, строчки кода вроде: "проверить что что-то — это именно то, что мы думаем".

И каждый раз, когда мы что-то меняем — добавляем новые возможности или меняем существующие — мы запускаем тесты, чтобы удостовериться, что ничего не поломалось.

Продолжайте и переходите к упражнению. Вы напишете несколько тестов самостоятельно!
Дополнение к уроку
Хекслет и console.log
Кнопка "Проверить" запускает на выполнение тесты, которые, в свою очередь, используют написанный вами код. Это означает, что вы легко можете использовать console.log везде, где хотите, и столько раз, сколько хотите. Весь вывод появится во вкладке OUTPUT. Обратите внимание на одну деталь. Если выполнение кода не дошло до того места, где была вставлена отладочная печать, то она, естественно, не выведется на экран.
Хитрость
Часто отладочную печать сложно заметить в объемном выводе тестов. Но если сделать так: console.log('!!!!!!', <то что вы хотите посмотреть>), то пропустить вывод станет практически невозможно.
Важное: пограничные случаи
При тестировании важно не забывать про так называемые пограничные случаи. Нетипичные случаи, находящиеся «с края» потенциальных вариантов.

Например, написанная функция factorial может работать на всех числах больше единицы, но не работать, если параметром будет единица или ноль.
Это, конечно же, является ошибкой. Пограничные случаи, как правило, требуют дополнительного кода для их обработки.

Важно не забывать писать тесты для таких вариантов использования функции.

Также пограничным случаем для функции factorial является любое отрицательное число.

Не существует никаких правил по тому, что является пограничным случаем, а что нет. Все зависит от конкретной задачи и вашего опыта.
Выводы
Тесты — это кусочки кода, которые проверяют, что другой код работает правильно. assert — это общее название для подобных тестовых функций, а так же название библиотеки тестирования JavaScript. Библиотека — коллекция полезных функций, небольшое количество кода других людей, которое вы можете добавлять в программу и упрощать свою работу.

Assert принимает выражение и жалуется, если оно не истинно. Следующий тест проверяет действительно ли square(2) это 4:

assert(square(2) === 4);
А в данном случае не равно ли square(6) числу 4:

assert(square(6) !== 4);
Если выражение утверждения истинно, то тест проходит.

А вот как он жалуется, если выражение не истинно:

Assertion error: false == true
  at square (/home/rakhim/code/planets.js:4:3)
  at surfaceArea (/home/rakhim/code/planets.js:8:10)
Это специальная отдельная функция для проверки равенства:

assert.equal(square(2), 4);
А вот как она жалуется, если тест валится:

Assertion error: 6 == 4
  at square (/home/rakhim/code/planets.js:4:3)
  at surfaceArea (/home/rakhim/code/planets.js:8:10)
Так лучше, потому что вы можете видеть реальные результаты (6) и ожидаемые результаты (4), и насколько они не соответствуют друг другу.
Дополнительные материалы
Тесты
Пройти тест
Может ли программа содержать несколько функций? (доверьтесь своим инстинктам ;)
Верно!
Неверно
Дальше
Проверить
Завершить тест
Могут ли функции возвращать строки (тексты), или они могут возвращать исключительно числа?
Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const height = findHeight();

Какое значение будет "записано" в константу height после выполнения?

Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const someFunction = (x) => {
  return 10 * 42;
};


const y = someFunction(9281);

Что будет "сохранено" в константе y после выполнения?
Неверно
Верно!
Неверно
Дальше
Проверить
Завершить тест
Каким будет результат вызова функции?

 const firstNum = 10;
 const secondNum = 5;


 const sum = (z, g) => z + g;


 sum(firstNum, secondNum);
Верно!
Неверно! Попробуйте еще раз
Неверно! Попробуйте еще раз
Дальше
Проверить
Завершить тест
Дана такая функция:

const sum = (a, b, c) => {
  return a + b + c;
};

Можно ли в этой ситуации использовать сокращенный синтаксис записи функции?
Неверно
Верно!
Дальше
Проверить
Завершить тест
Пройти еще раз
Пройти еще раз
Пройти еще раз
Пройти еще раз
Упражнение
Проверьте с помощью утверждений (asserts) правильность работы функции factorial(), которая вычисляет факториал переданного числа. Факториал числа x — это произведение всех чисел от 1 до x. Например, факториал числа 4 это 1 * 2 * 3 * 4 = 24.

Подробнее о факториале можно почитать в википедии.

Саму функцию в этой практике реализовывать не надо, она уже готова. Вам же сейчас надо дописать тесты для проверки её работы.

Несколько примеров:

factorial(0); // 1
factorial(1); // 1
factorial(5); // 120
Вам необходимо добавить минимум три дополнительные проверки (помимо уже существующих в модуле), а именно: протестировать пограничные случаи (вызов с параметрами 0 и 1) и сделать проверку на работу с другими неотрицательными целыми числами. Общепринятого факториала от целых отрицательных чисел не существует.
Подсказки
  • assert(<expression>) проверяет, что результат <expression> является истиной.
  • assert.equal(<actual>, <expected>) проверяет, что <actual> и <expected> равны (проверка на нестрогое равенство ==).