Статьи

Познакомьтесь с Крокфордом

Существуют десятки инфраструктур тестирования JavaScript, но большинство из них работают более или менее одинаково. Тем не менее, Aheck Дугласа Крокфорда значительно отличается от большинства. В этом уроке я покажу вам, как это отличается и почему вы должны рассмотреть возможность его использования!


Крокфорд описывает JSCheck как «инструмент тестирования, основанный на спецификациях.

Крокфорд описывает JSCheck как «инструмент тестирования, основанный на спецификациях». При использовании платформ, к которым вы привыкли, вы должны написать тест для заданного фрагмента функциональности и, если этот тест пройден, объявить, что данная функциональность работает правильно , Однако возможно, что вы пропустите некоторые крайние случаи или исключения, которые не охватываются вашими тестами.

Хотя раскрытие крайних случаев не является явной целью JSCheck, это хорошая дополнительная выгода. Основная идея JSCheck заключается в следующем: спецификация, которую вы пишете, фактически описывает, как должен работать код, который вы тестируете. Затем JSCheck возьмет эту спецификацию (называемую претензией в JSCheck-lingo) и сгенерирует случайные тесты для подтверждения претензии. Наконец, он сообщит вам результаты.

Звучит интересно? Читай дальше! Звучит знакомо? Возможно, вы использовали инструмент тестирования Haskell, QuickCheck , на котором основывался JSCheck.


Конечно, прежде чем писать заявление, нам нужно протестировать некоторый код. Недавно я написал мини- счетчик паролей, похожий на функционал на HowSecureIsMyPassword.net . Это на самом деле не фантазия: вы просто передаете функции пароль и получаете счет обратно. Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(function () {
    var PasswordScorer = {};
 
    PasswordScorer.score = function (password) {
        var len = password.length,
            lengthScore = 0,
            letterScore = 0,
            chars = {}
 
        if (len >= 21) { lengthScore = 7;
        else if (len >= 16) { lengthScore = 6;
        else if (len >= 13) { lengthScore = 5;
        else if (len >= 10) { lengthScore = 4;
        else if (len >= 8) { lengthScore = 3;
        else if (len >= 5) { lengthScore = 2;
 
        var re = [ null, /[az]/g, /[AZ]/g, /\d/g, /[!@#$%\^&\*\(\)=_+-]/g];
 
        for (var i = 1; i < re.length; i++) {
            letterScore += (password.match(re[i]) || []).length * i;
        }
 
        return letterScore + lengthScore;
    };
 
    (typeof window !== ‘undefined’ ? window : exports).PasswordScorer = PasswordScorer;
}());

Это довольно простой код, но вот что происходит: оценка состоит из двух промежуточных оценок. Существует начальная оценка, основанная на длине пароля, а затем дополнительная оценка для каждого символа, 1 балл за каждую строчную букву, 2 балла за каждую заглавную букву, 3 балла за каждое число и 4 балла за каждый символ ( из ограниченного набора).

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


Теперь мы готовы написать наши претензии. Сначала перейдите на страницу JSCheck Github и загрузите файл jscheck.js . Мне нравится запускать свои тесты в терминале через NodeJS, поэтому добавьте эту единственную строку в самый конец файла:

1
(typeof window !== ‘undefined’ ? window : exports).JSC = JSC;

Это никак не повлияет на поведение файла в браузере, но заставит его работать как модуль в Node. Обратите внимание, что файл jscheck.js представляет JSC в качестве единой глобальной переменной для всей библиотеки. Если бы мы не делали эту настройку, мы бы получили к ней доступ.

Давайте откроем passwordScorerSpec.js и начнем:

1
2
JSC = require(«./../vendor/jschec»;).JSC;
PasswordScorer = require(«./../lib/passwordScore»;).PasswordScorer;

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

Теперь мы готовы написать нашу первую заявку. Конечно, мы используем метод JSC.claim . Этот метод принимает три параметра, с необязательным четвертым. Первый параметр — это просто строка, имя для заявки. Второй параметр называется предикатом : это фактическая функция тестирования. Очень просто, эта функция должна возвращать true если утверждение истинно, и false если утверждение ложно. Случайные значения, которые JSCheck сгенерирует для теста, будут переданы в качестве параметров предикату.

Но как JSCheck узнает, какой тип случайных значений передать предикату? Вот где третий параметр, спецификатор вступает в игру. Это массив с элементом для каждого параметра предиката. Элементы в массиве определяют, какие типы следует отдавать предикату, используя функции спецификатора JSCheck. Вот несколько из них:

  • JSC.boolean() возвращает true или false.
  • JSC.character() принимает минимальный и максимальный символы и возвращает один символ из этого диапазона. Он также может взять односимвольный код и вернуть этот символ.
  • JSC.integer() вернет простое число. Или передайте ему один параметр, чтобы получить целое число (целое число) между 1 и параметром, или два параметра для целого числа в этом диапазоне.

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

1
2
3
4
5
6
JSC.claim(«All Lowercase Password»;, function (password, maxScore) {
  return PasswordScorer.score(password) <= maxScore;
}, [
  JSC.string(JSC.integer(10, 20), JSC.character(‘a’, ‘z’)),
  JSC.literal(26)
]);

Наш первый параметр — это имя. Второе — это функция тестирования: она получает пароль и максимальный балл и возвращает true, если балл для этого пароля меньше или равен максимальному баллу. Затем у нас есть массив спецификаторов. Наш первый параметр (пароль) должен быть строкой, поэтому мы используем метод JSC.string() : он может принимать два параметра: количество символов в строке и значение для этих символов. Как видите, мы просим пароль от 10 до 20 символов. Для значения мы используем метод JSC.characters() чтобы получить случайные символы между ‘a’ и ‘z’.

Следующее значение — наш параметр maxScore . Иногда мы не хотим случайности, которую предлагает JSCheck, и это один из таких случаев. Вот почему есть JSC.literal : передать буквальное значение предикату. В этом случае мы используем 26, что должно быть максимальным значением для любого строчного пароля от 10 до 20 символов.

Теперь мы готовы запустить тест.


Прежде чем мы на самом деле запустим заявку и получим отчет, нам нужно настроить функцию, которая будет получать отчет. JSCheck передает отчет в функцию обратного вызова JSC.on_report . Следовательно:

1
2
3
JSC.on_report(function (str) {
  console.log(str);
});

Ничего фантастического. Теперь осталось только вызвать JSC.check() . Теперь мы можем отправиться в наш терминал и запустить это:

1
node path/to/passwordScorerSpec.js

За кулисами JSCheck запускает предикат 100 раз, каждый раз генерируя разные случайные значения. Вы должны увидеть распечатанный отчет.

1
2
All Lowercase Passwords 100 of 100
pass 100

Они все прошли, но это не большая часть отчета, а? Что ж, если бы какой-либо из наших тестов не прошел, они были бы включены в отчет. Однако вы можете отрегулировать уровень вывода с JSC.detail функции JSC.detail : передайте ему число от 0 до 4 (включительно), чтобы получить что-либо, что не будет выводиться во все тестовые случаи. Значением по умолчанию является 3.


Помните, как я сказал, что JSC.claim может принять четвертый параметр? Он называется классификатором и получает те же параметры, что и предикат. Затем он может вернуть строку для классификации или группировки наших тестов. Я признаю, что я не был уверен, где это будет полезно, пока я не создал приведенный выше пример утверждения. Видите, я допустил ошибку в предикате и сравнил счет с maxScore с operator instead of the operator, so any passwords that scored 26 points were failing. I was seeing reports that looked something like this: operator instead of the operator, so any passwords that scored 26 points were failing. I was seeing reports that looked something like this:

1
2
3
4
5
6
All Lowercase Passwords 96 of 100
FAIL [12] («vqfqkqqbwkdjrvplkrx»;,26)
FAIL [21] («nhgkznldvoenhqqlfza»;,26)
FAIL [62] («eclloekuqhvnsyyuekj»;,26)
FAIL [78] («rvrkfivwtdphrhjrjis»;,26)
pass 96 fail 4

До сих пор не совсем понятно, почему некоторые тесты не проходят. Поэтому я добавил функцию классификатора, которая сгруппировала тестовые случаи по баллам: как я уже сказал, функция принимает те же параметры, что и предикат, и возвращает строку. Каждый тестовый случай, который возвращает одну и ту же строку из классификатора, будет сгруппирован в отчете.

1
2
3
function (password, maxScore) {
    return PasswordScorer.score(password) + » points»;;
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
All Lowercase Passwords 96 of 100
 FAIL [4] 26 points:(«illqbtiubsmrhxdwjfo»;,26)
 FAIL [22] 26 points:(«gruvmmqjzqlcyaozgfh»;,26)
 FAIL [34] 26 points:(«chhbevwtjvslprqczjg»;,26)
 FAIL [65] 26 points:(«kskqdjhtonybvfewdjm»;,26)
14 points: pass 8
15 points: pass 5
16 points: pass 12
18 points: pass 10
19 points: pass 12
20 points: pass 11
22 points: pass 12
23 points: pass 8
24 points: pass 10
25 points: pass 8
26 points: pass 0 fail 4

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


Итак, в конце концов, стоит ли его использовать? Вот что я думаю: это не то, что вы обязательно собираетесь использовать с каждой базой кода, но иногда вам будет полезно иметь возможность создавать случайные тестовые случаи, которые будут тщательно тестировать данный фрагмент кода. Когда это то, что вы хотите сделать, я не видел инструмент лучше для этого, чем JSCheck.

У JSCheck есть несколько других опций и куча спецификаторов, которые мы не рассмотрели в этом руководстве; отправляйтесь в JSCheck.og, чтобы прочитать о них. В противном случае, я хотел бы услышать ваши мысли о JSCheck в комментариях!