Добрый день хабр.
После моей статьи о test.it прошлавечность неделя. И как я не планировал растянуть этот срок хотя бы на месяц, но пришло время для новой публикации.
Картинка для привлечения внимания:
С того времени библиотека (во многом благодаря хабравчанам) обросла новым функционалом.
А ввиду того, что синтаксис, приведённый в прошлой статье, в текущей версии работает не полностью, откладывать эту статью ещё на 3 недели у меня нету права.
Кто не любит много слов — Сайт на котором можно увидеть код в действии, GitHub, Wiki
Появились цепочные вызовы
и новый механизм вложенности
Новый способ отображения ошибок
И два дополнительных метода test.typeof() и test.trace().
А также 3 Wiki-страницы.
И так. Разберём пример, приведённый в wiki:
Пока мы ещё не приступили к тестам, воспользуемся методом test.typeof().
Он умеет различать: Array, Boolean, Date, Error (EvalError, RangeError, ReferenceError, SyntaxError, TypeError, UriError), Function, NaN и Number, Object, RegExp, String, Window, HTML, NodeList. А ещё он пустую переменную определит как 'undefined' но в отличие от стандартного typeof не сможет получить в качестве аргумента не объявленную переменную. За то ответит 'undefined' на несуществующее свойство объявленного и непустого объекта. Но это уже специфика языка.
Если мы обновим страницу, то в консоли появится строка:
Теперь взглянем на метод test.trace().
На самом деле, это не настоящий trace() (которого, к сожалению, нету в JavaScript), потому что из него были вырезаны все упоминания о вызовах внутри библиотеки.
К выводу в консоль теперь добавится:
Здесь и далее не значащие части вывода консоли на скриншотах будут опускаться, что бы не увеличивать бессмысленно размер изображений.
Для начала создадим объект для тестов, переменную с целочисленным значением и пустую переменную.
Поехали дальше.
Следующий шаг — первый тест.
Тут всё как и раньше.
test.done() — завершает тесты и выводит результат в консоль.
Далее будет предполагаться, что test.done() идёт последней строкой нашего кода. В примерах я буду его опускать.
В консоли мы видим:
Где:
Если раскрыть эту группу, то можно увидеть список тестов/групп в ней.
Прежде чем разбирать наш единственный тест, давайте раскроем и его:
И так:
Самый элементарный случай — добавление комментария:
Этот код можно было бы записать и в виде:
Теперь root (раскрытый автоматически, потому что содержит непройденный тест) выглядит так:
В счётчиках в первой строке можно заметить увеличение второго числа с 0 до 1, что означает увеличение колличества проваленных тестов/групп.
Обратим своё внимание на, раскрытый по умолчанию (потому что не был пройден), тест.
Он отличается от предыдущего только статусом fail — означающим что тест провален, и комментарием «i badly taught algebra at school», который мы добавили.
Очевидно что в test.it( value ) можно передавать не только строки, но и переменные, и выражения, и вызовы функций и т.п. Впринципе, что бы мы не передали, сначала будет выполненно, получен результат, а этот результат уже и пойдёт в тест. Таков уж JavaScript.
Проверим только что сказанное. Протестируем выражение:
Пока не мы не ушли в дебри цепочных вызовов, а мы обязательно уйдём, посмотрим на другой вариант теста test.it().
Давайте сравним, объявленную ранее переменную с другим значением.
В консоли эти 2 теста будут выглядеть следующим образом:
Ничего необычного, но стоит обратить внимание на описание первого (проваленного) теста. "arguments has different types" — в этом тексте содержится подсказка, поясняющая нам почему тест был провален — переданные аргументы разного типа.
А теперь попробуем более сложные цепи.
Давайте выполним какое-нибудь действие, в зависимости от результата теста.
В этом коде, мы проверяем Family на не-ложность, и в зависимости от результата выводим в консоль разные фразы.
Вывод консоли теперь выглядит так:
Правда такую задачу предпочтительнее выполнять при помощи другого цепочного вызова:
В результате в консоли мы видим:
Эта конструкция предпочтительней, потому что не завершает цепочку.
Теперь создадим первую группу.
Но так как тестов переданно ен было, группа создаётся пустой, но со статусом pass — потому как в ней нету ниодного проваленного теста.
Перед тем как сделать скриншот, я расскрыл группу — это видно по повороту стрелки вниз, и двум серым пикселам, означающим её конец. Но так как группа пустая, она выглядит практически как закрытая.
Что ж. Приступим к более осмысленным действиям. Создадим группу с двумя тестами внутри и комментарием.
Тут стоит лишь обратить внимание на добавленный нами комментарий unite! — в заголовке группы.
А теперь провернём финт ушами, и добавим несколько тестов в уже созданную группу. И не просто тестов, а тестов которые инициируют новые тесты в зависимости от своего результата.
Добавим следующий код:
Вон видите в заголовке? 3 пройденных, 2 проваленных — всего 5.
Пора взглянуть на парочку необычных тестов. Для начала в группе «here comes strange tests» создадим следующие два теста:
test.types( values [, type] ) — как и test.them( values ) проверяет элементы массива, переданного первым аргументом. Но проверяет их на совпадение типов, а если передан второй аргумент type — то берёт этот тип в качестве образца.
У этого теста есть упрощённый аналог, но о нём чуть-чуть позже.
Вот так они выглядят в консоли:
Я раскрыл тесты вместе с их массивами аргументов для наглядности, но что-то мне кажется, наглядность от этого только уменьшалась.
А вот вам и ещё одна цепочная магия:
Поясню — первый тест, проверил значение Family.members на не ложность. Так как это массив из двух элементов — тест пройден.
arguments()[0] == Family.members. Следовательно в переменную numberOfMembers заносится количество элементов массива Family.members то бишь 2. Второй тест проваливается из-за того что 2 не больше 5.
Вы ведь ещё помните, что мы находимся в группе "here comes strange tests"?
Добавим сюда ещё одну группу, и сразу воспользуемся конструкцией for для того что бы создать несколько однотипных тестов.
Теперь эта новая группа "Members age" располагается в старой "here comes strange tests".
Добавим в эту же группу "Members age" ещё один тест:
В заголовке ошибки:
Затем идёт результат test.trace() что бы легче было найти её в коде. И сам объект ошибки, в данном случае RangeError — это если кому-то захочется покопаться в нём глубже.
Вернёмся на уровень root.
На всякий случай напомню, что группа "here comes strange tests" сейчас выглядит так:
В ней есть ещё одна группа "Members age". Вот в неё тест мы сейчас и добавим.
Вот последнее мы только что и сделали. Теперь в консоли видим:
И на последок, для закрепления всего выше сказанного:
Ах да. test.root всё ещё лежит на своём месте. Его всё также можно исползовать для создания новых вариантов отображения результатов. Он слегка упростился (у групп счётчики перестали разделять группы и тесты).
Пустой root выглядит так:
Очень хотелось бы поблагодарить:
Всё ещё остались минусы приведённые в прошлой стате. Но уже есть весьма интересные мысли по методам их решений.
Сайт на котором можно увидеть весь приведённый выше код в действии, GitHub, Wiki
После моей статьи о test.it прошла
Картинка для привлечения внимания:
С того времени библиотека (во многом благодаря хабравчанам) обросла новым функционалом.
А ввиду того, что синтаксис, приведённый в прошлой статье, в текущей версии работает не полностью, откладывать эту статью ещё на 3 недели у меня нету права.
Кто не любит много слов — Сайт на котором можно увидеть код в действии, GitHub, Wiki
Появились цепочные вызовы
test.it(some)
.comment('comment to test')
.callback(function(){alert('test has been passed')})
.arguments(); // -> [some]
test.group('group', function(){ ... })
.comment('comment to group')
.result(); // -> true/false
и новый механизм вложенности
test.group('first group',function(){
...
test.group('second group', function(){
...
});
...
});
test.group('first group').group('second group',function(){ ... });
Новый способ отображения ошибок
И два дополнительных метода test.typeof() и test.trace().
А также 3 Wiki-страницы.
А теперь обо всём этом поподробнее.
И так. Разберём пример, приведённый в wiki:
Пока мы ещё не приступили к тестам, воспользуемся методом test.typeof().
console.log(
test.typeof(1)
,test.typeof("text")
,test.typeof([1,2,3])
,test.typeof({a:1,b:2})
,test.typeof()
,test.typeof(document)
,test.typeof(document.getElementsByTagName("body"))
,test.typeof(window)
,test.typeof(/yes it is RegExp/) // and many many more ...
);
test.typeof() — определяет тип переданного ему значения.Он умеет различать: Array, Boolean, Date, Error (EvalError, RangeError, ReferenceError, SyntaxError, TypeError, UriError), Function, NaN и Number, Object, RegExp, String, Window, HTML, NodeList. А ещё он пустую переменную определит как 'undefined' но в отличие от стандартного typeof не сможет получить в качестве аргумента не объявленную переменную. За то ответит 'undefined' на несуществующее свойство объявленного и непустого объекта. Но это уже специфика языка.
Если мы обновим страницу, то в консоли появится строка:
Теперь взглянем на метод test.trace().
(function firstFunction() {
(function secondFunction() {
(function lastFunction() {
console.log(test.trace());
})();
})();
})();
test.trace() — возвращает список (собранный в строки разделённые "\n") строк кода, которые были выполнены для вызова этого метода.На самом деле, это не настоящий trace() (которого, к сожалению, нету в JavaScript), потому что из него были вырезаны все упоминания о вызовах внутри библиотеки.
К выводу в консоль теперь добавится:
Здесь и далее не значащие части вывода консоли на скриншотах будут опускаться, что бы не увеличивать бессмысленно размер изображений.
Давайте приступим к тестам.
Для начала создадим объект для тестов, переменную с целочисленным значением и пустую переменную.
var Family = {
name: "Desiderio",
pet: {
type: "dog",
name: "google"
},
members: [
{
name: "Titulus",
age: 23
},
{
name: "Dude",
age: Infinity
}
]
}
var myIQ = 100;
var Nothing;
Думаю вопросов касательно этого кода возникнуть не должно.Поехали дальше.
Следующий шаг — первый тест.
Тест на не-ложность.
Тут всё как и раньше.
test.it("hello world");
test.done();
test.it( value ) — создаёт новый тест, проверяя value на не-ложность.test.done() — завершает тесты и выводит результат в консоль.
Далее будет предполагаться, что test.done() идёт последней строкой нашего кода. В примерах я буду его опускать.
В консоли мы видим:
Где:
- root — имя группы нулевого уровня.
- pass — статус группы, означающий что все тесты/группы в ней пройдены успешно.
- 1/0/0 — количество соответственно пройденных/проваленных/ошибочных тестов/групп.
- (9 ms) — время в миллисекундах потраченное на тесты.
Если раскрыть эту группу, то можно увидеть список тестов/групп в ней.
Прежде чем разбирать наш единственный тест, давайте раскроем и его:
И так:
- pass — статус теста означающий что он пройден
- argument is not false — описание типа теста и его результата
- [«hello world»] — массив аргументов переданных тесту
Попробуем воспользоваться механизмом цепочности.
Самый элементарный случай — добавление комментария:
test.it(2+2==5)
.comment("i badly taught algebra at school");
.comment( text ) — добавляет комментарий к тесту/группе, в чьей цепочке он был вызван. При этом продолжая цепочку, но об этом чуть позже.Этот код можно было бы записать и в виде:
test.it(2+2==5).comment("i badly taught algebra at school");
Но, для случаев с более длинными цепочками, запись в столбик удобнее и нагляднее.Теперь root (раскрытый автоматически, потому что содержит непройденный тест) выглядит так:
В счётчиках в первой строке можно заметить увеличение второго числа с 0 до 1, что означает увеличение колличества проваленных тестов/групп.
Обратим своё внимание на, раскрытый по умолчанию (потому что не был пройден), тест.
Он отличается от предыдущего только статусом fail — означающим что тест провален, и комментарием «i badly taught algebra at school», который мы добавили.
Очевидно что в test.it( value ) можно передавать не только строки, но и переменные, и выражения, и вызовы функций и т.п. Впринципе, что бы мы не передали, сначала будет выполненно, получен результат, а этот результат уже и пойдёт в тест. Таков уж JavaScript.
Проверим только что сказанное. Протестируем выражение:
test.it(Infinity>Infinity-1)
.comment("philosophically is not it?");
Можете подумать об этом выражении за рюмочкой кофе, мы здесь для другого собрались. Результат теста выглядит как и результат предыдущего, что очевидно.Пока не мы не ушли в дебри цепочных вызовов, а мы обязательно уйдём, посмотрим на другой вариант теста test.it().
Тест на равенство
Давайте сравним, объявленную ранее переменную с другим значением.
test.it(myIQ,"genious")
.comment("is I'm genious?");
test.it(myIQ,(1+10)*12 - 34 + 5*5*5 - 123)
.comment("check my IQ to be a normal");
test.it( value1, value2 ) — проверяет равенство двух переданных ей значений.В консоли эти 2 теста будут выглядеть следующим образом:
Ничего необычного, но стоит обратить внимание на описание первого (проваленного) теста. "arguments has different types" — в этом тексте содержится подсказка, поясняющая нам почему тест был провален — переданные аргументы разного типа.
А теперь попробуем более сложные цепи.
Давайте выполним какое-нибудь действие, в зависимости от результата теста.
if (test.it(Family)
.comment("Is Family exist? Is it not empty?")
.result()) {
console.info("by if: ","Yep! Here it is!");
} else {
console.warn("by if: ","ALARM! there are no Family");
}
.result() — завершает цепочку и возвращает результат теста.В этом коде, мы проверяем Family на не-ложность, и в зависимости от результата выводим в консоль разные фразы.
Вывод консоли теперь выглядит так:
Правда такую задачу предпочтительнее выполнять при помощи другого цепочного вызова:
test.it(Nothing)
.comment("Is Nothing exist? Is it not empty?")
.callback(
function(){console.info("by callback: ","Yep! Here it is!");}
,function(){console.warn("by callback: ","ALARM! there are no Nothing");});
.callback( function(){ /* funcIfpass */}[, function(){ /* funcIffail */}[, function(){ /* funcIferror */}]]) — выполняет одну из трёх функций, в зависимости от результата прохождения теста или группы. При этом продолжая цепочку.В результате в консоли мы видим:
Эта конструкция предпочтительней, потому что не завершает цепочку.
Группы
Теперь создадим первую группу.
test.group("Empty group",function(){});
test.group( name, function(){… } ) — создаёт новую группу или обращается к уже существующей, и заполняет её тестами, находящимися в функции, переданной вторым аргументом.Но так как тестов переданно ен было, группа создаётся пустой, но со статусом pass — потому как в ней нету ниодного проваленного теста.
Перед тем как сделать скриншот, я расскрыл группу — это видно по повороту стрелки вниз, и двум серым пикселам, означающим её конец. Но так как группа пустая, она выглядит практически как закрытая.
Что ж. Приступим к более осмысленным действиям. Создадим группу с двумя тестами внутри и комментарием.
test.group('Family tests',function(){
test.it(Family.name,"Zukerberg")
.comment("Are we test Zukerberg's family?");
test.it(Family.name,"Desiderio")
.comment("Or Desiderio's?");
}).comment("unite!");
Вот видите — ничего сложного!Тут стоит лишь обратить внимание на добавленный нами комментарий unite! — в заголовке группы.
А теперь провернём финт ушами, и добавим несколько тестов в уже созданную группу. И не просто тестов, а тестов которые инициируют новые тесты в зависимости от своего результата.
Добавим следующий код:
test.group("Family tests",function(){
test.it(Family.pet)
.comment("Do we have a pet?")
.callback(function(){
// I add test in your test, so you can test while you testing
test.it(Family.pet,{type:"dog", name:"google"})
.comment("Is it right pet?");
});
test.it(Family.house)
.comment("Do we have a House?")
.callback(function(){
// next test will not be executed
test.it(Family.pet,{type:"Huge", color:"green"})
.comment("Is it right House?");
});
});
Учитывая прошлые 2 теста в этой группе и, описанные только что, ещё 4 теста, всего их будет = 5(sic!). Можете проверить на калькуляторе.Вон видите в заголовке? 3 пройденных, 2 проваленных — всего 5.
Новые тесты
Пора взглянуть на парочку необычных тестов. Для начала в группе «here comes strange tests» создадим следующие два теста:
test.them([Family.pet, Family.members])
.comment("There must be memebers with pet, to call it a 'Family'");
test.types([Family.pet.name, Family.name],"string")
.comment("Is names are string type");
test.them( values ) — аналог test.it( value ), только в качестве первого аргумента берёт массив, в котором уже проверяет элементы на не-ложность.test.types( values [, type] ) — как и test.them( values ) проверяет элементы массива, переданного первым аргументом. Но проверяет их на совпадение типов, а если передан второй аргумент type — то берёт этот тип в качестве образца.
У этого теста есть упрощённый аналог, но о нём чуть-чуть позже.
Вот так они выглядят в консоли:
Я раскрыл тесты вместе с их массивами аргументов для наглядности, но что-то мне кажется, наглядность от этого только уменьшалась.
А вот вам и ещё одна цепочная магия:
var numberOfMembers = test.type(Family.members,"Array")
.comment("Is it a several members, nor a single member?")
.arguments()[0].length;
test.it(numberOfMembers>5)
.comment("Is it big family?");
.arguments() — завершает цепочку вызовов и возвращает аргументы переданные в тест (не в группу!).Поясню — первый тест, проверил значение Family.members на не ложность. Так как это массив из двух элементов — тест пройден.
arguments()[0] == Family.members. Следовательно в переменную numberOfMembers заносится количество элементов массива Family.members то бишь 2. Второй тест проваливается из-за того что 2 не больше 5.
Вложенность
Вы ведь ещё помните, что мы находимся в группе "here comes strange tests"?
Добавим сюда ещё одну группу, и сразу воспользуемся конструкцией for для того что бы создать несколько однотипных тестов.
test.group("Members age",function(){
for (i=0;i<numberOfMembers;i++) {
test.it(Family.members[i].age>25)
.comment("Is "+Family.members[i].name+" older then 25?");
}
});
Теперь эта новая группа "Members age" располагается в старой "here comes strange tests".
Ошибки
Добавим в эту же группу "Members age" ещё один тест:
test.it()
.comment("yep, here is error");
Такой код приведёт к ошибке, потому как test.it() ожидает получить от 1 до 2 аргументов.В заголовке ошибки:
- RangeError — тип ощибки
- at least one argument expected — описание, помогающее понять причину её возникновения.
Затем идёт результат test.trace() что бы легче было найти её в коде. И сам объект ошибки, в данном случае RangeError — это если кому-то захочется покопаться в нём глубже.
Ссылки на группы
Вернёмся на уровень root.
На всякий случай напомню, что группа "here comes strange tests" сейчас выглядит так:
В ней есть ещё одна группа "Members age". Вот в неё тест мы сейчас и добавим.
test.group("here comes strange tests").group("Members age",function(){
test.it("bye")
.comment("good");
});
test.group( name ) — возвращает ссылку на группу, после чего её можно использовать как начало цепи, для добавления новой группы тестов или для добавления тестов в уже существующую подгруппу.Вот последнее мы только что и сделали. Теперь в консоли видим:
И на последок, для закрепления всего выше сказанного:
полный листинг со всем выводом в консоль
console.log( // look how do test.typeof() work
test.typeof(1)
,test.typeof("text")
,test.typeof([1,2,3])
,test.typeof({a:1,b:2})
,test.typeof()
,test.typeof(document)
,test.typeof(document.getElementsByTagName("body"))
,test.typeof(window)
,test.typeof(/yes it is RegExp/));
(function firstFunction() { // look how do test.trace() work
(function secondFunction() {
(function lastFunction() {
console.log(test.trace());
})();
})();
})();
var Family = { // Here some complex object
name: "Desiderio",
pet: {
type: "dog",
name: "google"
},
members: [
{
name: "Titulus",
age: 23
},
{
name: "Dude",
age: Infinity
}
]
}
var myIQ = 100; // and value
var Nothing; // and empty value
test.it("hello world"); // Let"s add some simple tests
test.it(2+2==5).comment("i badly taught algebra at school"); // with comment
test.it(Infinity>Infinity-1).comment("philosophically is not it?"); // with expression
// check equalence
test.it(myIQ,"genious").comment("is I'm genious?");
test.it(myIQ,(1+10)*12 - 34 + 5*5*5 - 123).comment("check my IQ to be a normal");
// try some chain staff
if (test.it(Family).comment("Is Family exist? Is it not empty?").result()) {
console.info("by if: ","Yep! Here it is!");
} else {
console.warn("by if: ","ALARM! there are no Family");
}
// do it again in better way
test.it(Nothing).comment("Is Nothing exist? Is it not empty?").callback(
function(){console.info("by callback: ","Yep! Here it is!");}
,function(){console.warn("by callback: ","ALARM! there are no Nothing");});
test.group("Empty group",function(){}); // try to make a group
test.group('Family tests',function(){ // let's unite it!
test.it(Family.name,"Zukerberg").comment("Are we test Zukerberg's family?");
test.it(Family.name,"Desiderio").comment("Or Desiderio's?");
}).comment("unite!");
test.group("Family tests",function(){ // and add some test after
test.it(Family.pet).comment("Do we have a pet?")
.callback(function(){
// I add test in your test, so you can test while you testing
test.it(Family.pet,{type:"dog", name:"google"}).comment("Is it right pet?");
});
test.it(Family.house).comment("Do we have a House?")
.callback(function(){
// next test will not be executed
test.it(Family.pet,{type:"Huge", color:"green"}).comment("Is it right House?");
});
});
test.group("here comes strange tests",function(){
// test existance of most important Family properties
test.them([Family.pet, Family.members])
.comment("There must be memebers with pet, to call it a 'Family'");
// test types of names
test.types([Family.pet.name, Family.name],"string")
.comment("Is names are string type");
// here some magic
var numberOfMembers = test.type(Family.members,"Array")
.comment("Is it a several members, nor a single member?")
.arguments()[0].length;
test.it(numberOfMembers>5).comment("Is it big family?");
// So if we know how many members there, lets check their age
test.group("Members age",function(){
for (i=0;i<numberOfMembers;i++) {
test.it(Family.members[i].age>25)
.comment("Is "+Family.members[i].name+" older then 25?");
}
test.it().comment("yep, here is error"); // add some error to see the trace
});
});
// add final test deep in group
test.group("here comes strange tests").group("Members age",function(){
test.it("bye").comment("good");
});
test.done();
root
Ах да. test.root всё ещё лежит на своём месте. Его всё также можно исползовать для создания новых вариантов отображения результатов. Он слегка упростился (у групп счётчики перестали разделять группы и тесты).
Пустой root выглядит так:
{
"type": "group",
"name": "root",
"time": 0,
"result": {
"pass": 0,
"fail": 0,
"error": 0,
"total": 0
},
"stack": []
}
Заключение
Очень хотелось бы поблагодарить:
- camelos и zorro1211 за идеи цепочных вызовов
- camelos отдельно за идею .callback
- Anonym за идею доступа к группе извне. в том числе и из других файлов.
Всё ещё остались минусы приведённые в прошлой стате. Но уже есть весьма интересные мысли по методам их решений.
Сайт на котором можно увидеть весь приведённый выше код в действии, GitHub, Wiki