Pull to refresh

Comments 55

Печально, что только консоль — мобильные браузеры в пролете, например. Я бы предложил вынести собственно представление результатов в какую-то отдельную часть, чтобы можно было заменить ее на рендеринг HTML.
И чего ради вы зациклились на QUnit'e? Это далеко не единственная библиотека для TDD/BDD/юнит-тестов.
Именно по этому сначала заполняется test.root, а затем вывод в консоль строится по нему. Так что да другой вариант вывода вполне можно реализовать. Просто первоначальная цель стояла именно в выводе в консоль. Измените
    var _done = function(obj) {
        /** update time in root */
        root.time = new Date().getTime() - root.time;

        /** display root */
        // console.dir(root);
        _printConsole(root);
    }
на
    var _done = function(obj) {
        /** update time in root */
        root.time = new Date().getTime() - root.time;

        /** display root */
        // console.dir(root);
        _printWhereEverYouWant(root);
    }
Предварительно конечно написав метод _printWhereEverYouWant(root); — и наслаждайтесь результатом.

Если есть более грамотный способ выделить вывод результатов, то с нетерпением жду pull-request или комментария об этом тут.
Простите, но «Моча + Чай» — это звучит сильно! )) Такое впечатление, что соотечественники повсюду)
Любимый фреймворк Геннадия Петровича Малахова, говорят.
Mocha за авторством TJ Holowaychuk'а, так же из под его клавиатуры вышли expressjs, connect и кучка других популярнейших модулей для nodejs. Где вы тут соотечественника узрели?
"mocha" произносится как "мокка" или "мокке" в данном случае (вики). Это напиток на основе кофе.
"chai" — это действительно чай, только от китайского "茶" ("ча"). Русское слово "чай" произошло от него же.
Оно же мокко. Но мы как-то долго недоумевали от надписей на бумажном стаканчике: espresso, cappuccino, latte, mocha… Сошлись на том, что в этих стаканчиках, в принципе, можно сдавать анализы.
Говорят ещё Safari, но у меня нету устройств Apple для проверки


можно взять устройство на Windows и поставить сафари на него
support.apple.com/kb/DL1531
Это же 5.1.7. Apple перестала выпускать версии под Windows с 6-го релиза.
тогда извиняюсь за дезинформацию — не обратил внимание на версию, а что Safari под Windows вообще прикрыли даже не знал
Любопытно, но:
1. Мне кажется: argument existS всё таки
2. Когда нет равенства аргументов (пример с url-ом хабра), то надо писать arguments are not equal — то есть состояние, а не цель проверки.
1. спасибо, если бы не подруга, знающая английский, readme вообще было бы невозможно читать diff
2. когда я говорил про подсказки — это и имелось ввиду. Более того, подсказки будут интеллектуальными. Например если аргументы разных типов, в подсказке будет об этом речь.
по п1: у вас есть отличный ресурс ;)
по п2: развивайте проект! начало хорошее.
Re: по п2: очень ждал этих слов. спасибо, буду стараться.
ещё подумалось, что было бы классно так (текучий интерфейс?):
test.it(some).comment('my comment');
да. скорее всего следующий коммит будет это реализовывать.
Просто первоначально я думал что лучше возвращать true или false, что бы можно было делать конструкции типа
if (test.it(something)) { doSomething(); }

но уже во время написания статьи понял что лучше возвращать test, дабы обеспечить цепочную связность
test.it(something).comment('my comment');
именно по этому не описывал в статье идею с if (test.it())

Главное что будет обратная совместимость. Можно будет комментарии добавлять так, как указано в статье, и новым способом.
я бы тогда подумал над реализацией тернарного оператора, как то так (только как идея):
test.it(something, callback_if_true, callback_if_false [, callback_finally]).comment('comment for true', 'comment for false')
Мне очень не хочется уходить от
switch (arguments.length) {
    /** in case of no arguments - throw Reference error */
    case 0 : { ... } break;
    /** if there only one argument - test it for truth */
    case 1 : { ... } break;
    /** if there are two arguments - test equalence between them */
    case 2 : { ... } break;
в test.it. возможно просто реализую это в отдельном методе, test.withCallback например.
Так или иначе идея хорошая, обязательно подумаю об этом.
Вполне вариант завести под это отдельную функцию. Правда я сторонник единого интерфейса в этом случае.
Пришла идея сделать callback так же как comment. Тогда будет возможно писать вот такие цепочики:
test.it(some)
    .callback(function(){ ... },function(){ ... })
    .comment('sometext');

Надо подумать. может есть ещё какой функционал, которым можно будет обвешать тесты, подобным образом.
Этот вариант мне нравится больше, но тогда уже лучше test.callback(...).it(...)? Чтобы внутри it можно было callback уже вызвать.
Ну, делаем test.callback(колбек).it(some), чтобы на момент запуска .it(some) callback был уже установлен, и внутри it можно было бы его вызвать. Как вам такой вариант?
Вариант имеет право на существование, но я не понимаю чем он лучше
test.it(some).callback(function(){ ... },function(){ ... })
Тут коллбек выбирает функцию для исполнения, основываясь на результате теста.
Это продолжает логику цепочности — когда начинает цепочку it(), а уже все остальные звенья отталкиваются от его результатов.
Сегодня уже реализовал цепочки:
// результат
test.it('single').comment('comment').result(); // -> true
test.it('first','second').comment('comment').result(); // -> false
// аргументы
test.it('single').comment('comment').arguments(); // -> 'single'
test.it('first','second').comment('comment').arguments(); // -> ['first','second']

Я не вижу смысла менять последовательность, если результат не измениться.
Есть какая-то особенность вашего вариант, которую я не понимаю? В чём преимущество задавать коллбеки до, если функция в любом случае будет выбираться на основе прохождения теста?
Серьёзно, если есть преимущество — скажите какое. Я как раз на днях буду реализовывать этот функционал, и хочется сделать обоснованный выбор между этими реализациями.
UFO just landed and posted this here
Отличные идеи! спасибо, обязательно реализую их.
Уже готово. сегодня запушу. Ещё раз спасибо за идеи.
UFO just landed and posted this here
Было бы замечательно, если бы в test.group(groupname, fun) можно бы было передавать контекст исполнения функции fun.
Идея отличная, но вот что не нравится сразу:
  1. Если вы переопределите функции console.*, вы не потеряете связь консоли с кодом и будет отображаться файл и номер строки где тест провален (а не номер строки в вашем скрипте)
  2. Код был бы приятнее и удобнее, если бы выглядел как-то так:
    var testObject = TestFactory->create(testName, parentGroupName | null);
    var Me = {name:'Titulus',lastName:'Desiderio'};
    testObject
      ->test(Me, 'Я существую?')
      ->test(OtherObject);
    Me.habr = 'Хабрахабр!';
    testObject->test(Me.habr, 'Хабр?');
    

    Это бы позволило в произвольном месте в коде делать конструкции типа
    var testObject = TestFactory->getTest(testName);
    testObject->test(Obj, 'Возможно здесь тест пройдет');
    

Можно поподробнее о п.1? Меня как раз заботил вопрос, как бы выводить правильный trace()
По поводу п.2 — Боюсь что в ближайшем будущем я ни то что сделать, а просто понять такое не смогу.
п1. Попробуйте использовать window.onerror. Например перед запуском подменять его, выкидывать исключение если тест завален, в window.onerror вы увидите номер строки и файл.
Еще посмотрете на javascript-stacktrace.

Развивайте проект.
По первому пункту вот статья, а вот гитхаб.
Будут вопросы — пишите в личку, расскажу подробнее о нюансах «подмены консоли».
> testObject->test

Какой-то странный синтаксис
я имел в виду исключительно "->" :)
Это опечатка головного мозга у меня.
Стоп, п2 это просто php код?
Я уж думал я пропустил какую-то весьма важную часть js
Если мне память не изменяет, то
testObject->test(Me.habr, 'Хабр?');
это
testObject.test(Me.habr, 'Хабр?');

Так зачем заставлять пользователя писать
testObject = TestFactory.create(testName, parentGroupName | null);
если в библиотеке и так есть
window.test = new testit();

идея в том что бы переименовать конструктор testit в TestFactory, метод it в test, отдать создание объекта test пользователю, и переписать всё на PHP?
MuLLtiQ, titulusdesiderio: Отвечу всем сразу. Придрались как могли ) Очевидно же, что случайно в голове смешались два языка. Конечно, я имел ввиду нечто типа
var testObject = TestFactory.create(testName, parentGroupName | null);
var Me = {name:'Titulus',lastName:'Desiderio'};
testObject
  .test(Me, 'Я существую?')
  .test(OtherObject);
Me.habr = 'Хабрахабр!';
testObject.test(Me.habr, 'Хабр?');

идея в том что бы переименовать конструктор testit в TestFactory, метод it в test, отдать создание объекта test пользователю, и переписать всё на PHP?

А теперь с комментариями попытаюсь объяснить идею:
// TestFactory - глобальный объект, хранит в себе всю логику и все вызовы тестов
var testGroup = TestFactory.create(groupName, parentGroupName | null); // Создает новую группу тестов. Она может быть корневой или дочерней
// Далее идут тесты в группе
// Тесты в эту группу могут попасть и из других участков кода при необходимости
testGroup
  .test(Me, 'Я существую?')
  .test(OtherObject);
testGroup.test(Me.habr, 'Хабр?');


// Другой пример использования
var testGroup = TestFactory.get(groupName); // Получаем группу по имени
testObject.test(Obj, 'Возможно здесь тест пройдет'); // Добавляем тест в существующую группу

Мне такой подход кажется более логичным и лаконичным
Мне нравится идея обращения к группе из любого места в коде. Но не так как описали вы.
Думаю у меня получится реализовать это и без смены текущего синтаксиса
test.group('groupName',function(){ ... }) // Первый вызов группы groupName
test.it(...);
test.group('groupName',function(){ ... }) // Второй вызов группы groupName
То есть группы с одинаковыми groupName будут объединяться в одну.

А в вашем подходе мне не нравятся 2 момента
  1. Синтаксический сахар, Теперь группу приходиться идентифицировать не только по groupName, но и по переменной, которой эта группа присвоена.
  2. Следующий код вылетит с ошибкой RequestError, которая не будет отловлена. Или я не понимаю как её отловить
    var testGroup = TestFactory.get(groupName);
    testObject.test(h.a.b.r, 'Возможно здесь тест пройдет');
    
Я ведь не предлагаю вам готовое решение, это лишь мое видение с первого взгляда.
  1. Вы не совсем верно поняли. Группу можно идентифицировать по имени группы в любом месте как-то так:
    var testGroup = TestFactory.get('core_tests');
    

    А дальше вы можете добавлять тесты в эту группу, вам не надо будет где-то в недрах выискивать группу по имени, вы получите полноценный объект группы, которым можно будет удобно оперировать. Как только переменная станет не нужна, забудьте о ней — группа и тесты сохранятся в глобальном TestFactory.
  2. Вообще там опять опечатка (сегодня что-то невнимательный я), не testObject, а testGroup.
    Можно сделать так:
    var testGroup = TestFactory.getOrCreate(groupName); // Будет получена существующая группа или создана новая корневая
    testGroup.test(h.a.b.r, 'Возможно здесь тест пройдет');
    

    А можно так:
    var testGroup = TestFactory.get(groupName);
    if (testGroup) {
      testGroup.test(h.a.b.r, 'Возможно здесь тест пройдет');
    }
    



И еще по поводу синтаксиса:
test.group('groupName',function(){ ... }) // Первый вызов группы groupName
// Тут много много кода
test.it(...); // Как понять и не запутаться, в какую группу попадет этот тест?
Как понять и не запутаться, в какую группу попадет этот тест?

Обычно это решается табуляцией. так можно наехать и на стандартный синтаксис.
function myFunction(){ ... }) // Вызов функции
// Тут много много кода
var myVar = 10; // Как понять и не запутаться, где находится переменная myVar, в области видимости функции или в глобальной.


if (testGroup) {
  testGroup.test(h.a.b.r, 'Возможно здесь тест пройдет');
}

Точно так же выбросит ошибку и прервёт выполнение дальнейшего кода.
Это всё-равно синтаксический сахар, который может привести к печальным последствиям из-за невнимательности/неслаженности команды разработчиков:
var testGroup = TestFactory.getOrCreate(groupName1);
testGroup.test(some[0]);
testGroup.test(some[1]);
// код
var testGroup = TestFactory.getOrCreate(groupName2); // переопределяем testGroup
// ещё код
testGroup.test(some[3]);
В этой ситуации, если применять одну и ту же переменную, как буфферный testGroup, можно не заметить её переопределение и запутаться в тестах. А если использоватья каждый раз новую переменную с другим названием, то зачем разработчику придумывать два разных названия для идентификации одного и того же объекта? Есть и третий вариант, использовать разные названия, для разных переменных, но совпадающие с названиями групп
var groupName1= TestFactory.getOrCreate('groupName1'); // определяем первую группу
var groupName2= TestFactory.getOrCreate('groupName2'); // определяем вторую группу
Но вот тут встаёт вопрос о сахаре — зачем нам в двух местах писать одно и то же?

Если я вас не правильно понял, то поправьте пожалуйста. Я на самом деле не пытаюсь ругаться или защищать свою позицию любой ценой. Просто я не понимаю преимущества вашего подхода, а недостатки я описал выше
Вы наверное не работали с большими проектами на JS. Когда очень много файлов, классов, сотни объектов и сложная структура приложения, у вас не получится просто «взять и протестировать здесь и сейчас». С моим подходом вы сможете в разных местах вашего приложения (в разных файлах/объектах) добавлять тесты в одну и ту же группу.
Представьте, что у вас есть приложение. Для примера, есть несколько файлов, в каждом файле описывается отдельный компонент, назовем условно: ядро, внешняя библиотека ядра, контроллер, шаблонизатор. Вы сможете создать группу теста в ядре, во внешней библиотеке добавить тесты в эту же группу, в контроллере создать группы теста контроллера и отображения, в шаблонизаторе добавить тесты в группу, созданную в контроллере и т.п.

А о переопределении переменной вам подскажет IDE, точно также, как и об области видимости, в которой она объявлена.
Кроме Mocha есть Jasmine, один из самых популярных фреймворков для тестирования JS кода. Да и вообще JS код тестировать начали не вчера. Хотя свои велосипеды в начале пути писать в целом полезно…
Тоже хотел про нее сказать, но почему-то был уверен, что она выходит исключительно в виде gem`а.
Да и Konacha как-то интереснее имхо.
Вариант для Node.JS будете делать? :)
К сожалению никогда с Node.js не работал. Так что либо для начала мне надо разобраться с ним, либо с удовольствием приму pull-request.
При условии что это вообще возможно конечно (:
Sign up to leave a comment.

Articles

Change theme settings