diff --git a/README.md b/README.md index 0cf77f9..92dbbce 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,53 @@ # Yatra -## Что это? +Yatra (Yet Another Test Runner) – "еще один" исполнитель модульных тестов (unit-test runner). -Yatra (Yet Another Test Runner) – Это "еще один" исполнитель модульных тестов (unit-test runner). +В основном `Yatra` расчитан на тестирование фреймворка [`basis.js`](https://github/basisjs/basisjs) и проектов, разрабатываемых с его применением. Но это не является обязательным требованием и `Yatra` может использоваться в проектах и без `basis.js`. -В основном он расчитан на тестирование фреймворка `basis.js` и проектов, разрабатываемых с его применением. В этом случае можно воспользоваться преимуществами модульной системы фреймворка. Но это не является обязательным требованием и `Yatra` может использоваться в проектах без него. +Ключевыми особенностями являются удобная работа с тестами, атоматическое обновление тестов и их прогон при изменении тестируемого кода (не требуется самостоятельно обновлять страницу и перезапускать тесты), человекопонятная информация о результатах прохождения теста. -Ключевыми особенностями являются атоматическое обновление тестов и их прогон при изменении тестируемого кода (не требуется самостоятельно обновлять страницу), а так же более человекопонятная информация о результатах прохождения теста, нежели в других тестирующих решениях. - -На данный момент, реализован лишь базовый функционал и запуск тестов возможен только в браузере. В будущем возможности будут расширяться (см. [TODO](todo)), ускоряя и упрощая процесс модульного тестирования. +На данный момент, реализован не весь планируемый функционал. В будущем возможности будут расширяться (см. [TODO](todo)). ## Как использовать -Чтобы использовать runner, нужно добавить в проект сборку reporter'а и настроить его использование. +`Yatra` поставляется в трех видах: + +- `reporter` – как приложение; +- `lib` – как библиотека, для встраивания в другие интерфейсы; +- `runner` – непосредственно иполнитель тестов, не включает в себя интерфейс; предназначен для использования с другими тестирующими фреймворками и системами; ### Установка -Команды для локальной установки нужно скопировать файлы из папки `build` этого репозитория в проект, где планируется использование, например, в папку `test/runner`. +Для установки сборки потребуется `bower` или `npm`: + + > npm install yatra --save-dev + > bower install yatra --save -### Сборка +### Использование в исходном виде -Если необходимо сделать сборку, то для этого нужны инструменты [`basisjs-tools`](https://github.com/basisjs/basisjs-tools). Полный список команд: +Можно использовать `Yatra` и в исходном виде. Для этого необходимо клонировать репозитарий и установить зависимости: - > git clone https://github.com/basisjs/test-runner.git - > cd test-runner + > git clone https://github.com/basisjs/yatra.git yatra + > cd yatra > bower install - > basis build -После выполнения этих команд, результат сборки окажется в папке `build`. +Основной файл интерфейса – `src/reporter.html`. -### Использование в проекте +Для сборки необходимы инструменты [`basisjs-tools`](https://github.com/basisjs/basisjs-tools). Их можно установить локально используя `npm`: + + > npm install + > node node_modules/basisjs-tools/bin/basis build -Когда сборка reporter'а добавлена в проект, нужно настроить его использование. В данном репозитории, можно найти примеры настройки для проектов на `basis.js` [example/basis_setup](example/basis_setup) и для проектов без него - [example/non_basis_setup](example/non_basis_setup). Можно скопировать содержимое из нужной папки в папку `test` проекта. Если в ней находится папка `runner`, то больше ничего менять не нужно (только в настройке для `basis.js` нужно изменить путь к основному файлу фреймворка). +Результат сборки окажется в папке `build`. По умолчанию собирается только приложение. Для сборки `lib` или `runner`, нужно указывать явно необходимый файл. -Основное отличие настроек в том, что в случае `basis.js` набор тестов можно разбить на множество файлов, а в случае изменении тестов или тестируемого кода тесты будут самостоятельно перезапускаться. Без `basis.js` всего этого не будет. + > node node_modules/basisjs-tools/bin/basis build src/lib.html + > node node_modules/basisjs-tools/bin/basis build src/runner.html -Для запуска тестов (пока только в браузере), в браузере нужно открыть папку `test`, например, так `http://localhost/test` (адрес может быть другим, в зависимоти от ваших настроек). При открытии адреса сделается перенаправление на `reporter` и откроется его интерфейс. Останется только нажать кнопку `Run` в правом верхнем углу, чтобы запустить выполнение тестов. +### Использование в проекте + +Когда сборка `Yatra` добавлена в проект, нужно настроить его использование. В данном репозитории, можно найти примеры настройки для проектов на `basis.js` [example/basis_setup](example/basis_setup) и для проектов без него - [example/non_basis_setup](example/non_basis_setup). + +// TODO [Пример использования](https://github.com/basisjs/basisjs/tree/master/test) можно посмотреть в репозитории `basis.js`, выглядит это так: @@ -49,12 +61,12 @@ Yatra (Yet Another Test Runner) – Это "еще один" исполните - [ ] выводить в summary не только ошибки, но информацию об общем ходе выполнения тестов - [x] beforeEach/afterEach (+done/async) - [ ] возможность задавать порог, что тест медленно отрабатывает -- [ ] возможность быстрой отладки +- [х] возможность быстрой отладки - [ ] возможность делать тесты производительности - [ ] возможность использовать различные assert библиотеки - [ ] добавить различные интерфейсы: bdd, tdd, exports, qunit -- [ ] автоматизивароть сборку и зарегистрировать в bower -- [ ] прогон тестов используя node.js (зарегистрировать в npm) +- [х] автоматизивароть сборку и зарегистрировать в bower +- [х] прогон тестов используя node.js (зарегистрировать в npm) - [ ] поддержка code coverage - [ ] интеграция с travis ci @@ -82,16 +94,16 @@ var myTestSuite = { }; ``` -Если значением поля `test` является функция, то это сам тест. Если массив, то набор тестов (`suite`). +Если значением поля `test` является функция, то это непосредственно сам тест. Если массив, то набор тестов (`suite`). -Если используется [`basis.js`](https://github.com/basisjs/basisjs), то тесты и их наборы можно выносить в отдельные файлы используя функцию `require`. Это облегчает навигацию по тестам. +Когда используется [`basis.js`](https://github.com/basisjs/basisjs), тесты и их наборы можно выносить в отдельные файлы и подключать используя функцию `require`. Это облегчает навигацию по тестам. -Обычно описывается корневой пакет тестов (`index.js`), который выглядит вот так: +Обычно описывается корневой пакет тестов (`index.js`), который может выглядеть так: ```js // test suite module.exports = { - name: 'RNA test suite', + name: 'Example test suite', html: __dirname + 'env.html', // базовый файл окружения test: [ require('./spec/suite1.js'), @@ -102,11 +114,11 @@ module.exports = { }; ``` -Этот файл подключает другие файлы, являясь своего рода входной точкой. Тесты описываются в отдельных файлах и, обычно, располагаются в папке `spec`. +Этот файл подключает другие файлы, являясь своего рода входной точкой. Наборы тестов описываются в отдельных файлах и, обычно, располагаются в папке `spec`. -Свойство `html` задает файл, который будет использоваться для задания окружения. Такой файл загружается в `iframe`, а код тестов выполняется в рамках этого фрейма. Значение этого свойства наследуется вложенными тестами, потому не нужно его задавать без необходимости. +Свойство `html` задает файл, который будет использоваться для задания окружения выполняемым тестам. Такой файл загружается в `iframe`, и код тестов выполняется в рамках этого фрейма. Значение свойства `html` наследуется вложенными тестами. При необходимости его можно переопределить для определенного теста или поддерева тестов. -Типовой пакет (`suite`) выглядит так: +Типовой набор тестов (`suite`): ```js module.exports = { @@ -120,27 +132,35 @@ module.exports = { }; ``` -Свойство `init` является необязательным и позволяет задать функцию инициализирующее окружение. Этот код будет выполнен один единственный раз в момент инициализации, перед выполнением первого теста. Нужно иметь ввиду, что код этой функции выполняется в отдельном окружении (в отдельном `iframe`), и потому у него будет область видимости отличная от той, в которой описывается сама функция. То же касается и самих тестов. +Свойство `init` позволяет задать функцию инициализирующее окружение, является необязательным. Этот код будет выполнен один единственный раз в момент инициализации, перед выполнением первого теста в поддереве тестов. Стоит иметь ввиду, что код этой функции выполняется в отдельном окружении (в рамках `iframe`), и потому у него будет область видимости отличная от той, в которой описывается сама функция. То же касается и самих тестов. -Переменные объявленные в `init` будут доступны всем тестам. В упрощеном виде это работает так: +Переменные объявленные в `init` будут доступны всем тестам. Упрощенный код как это работает: ```js +// выбираем код тела функции init и выполняем его в текущем окружении eval(getFunctionBody(testSuite.init)); +// функция которая исполняет код тестов function runTest(code){ + // выполняется код тела функции, но сама функция не вызывается eval(getFunctionBody(code)); } +// тесты выполняются один за другим runTest(test1); runTest(test2); -... +// ... ``` Если у теста (или набора) есть свойство `html` или `init`, то для него и его вложенных тестов создается собственное окружение (отдельный `iframe`). ### Утверждения -В тестах выполняемый код должен сопровождаться утверждениями (`assertion`). Для проверки утверждения используется функция `assert`, которая доступна любому тесту как локальная переменная. Функция может принимать от одного до двух аргументов: +В тестах выполняемый код должен сопровождаться утверждениями (`assertions`). Ключевое отличие от других систем утверждений заключается в том, что если утверждение неверно, то тест продолжает выполняться. Таким образом, если в тесте использовано множество утверждений, то можно увидеть результат по всем, а не только по первому. Чаще всего это дает более полное представление о проблеме. + +![Несколько неверных утверждений](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/several-assert.png) + +Для проверки утверждения используется функция `assert`, которая доступна как локальная переменная. Функция может принимать один или два два аргумента: ```js assert(actual); // проверяется, что значение правдиво, то есть @@ -151,43 +171,52 @@ assert.deep(expected, actual); // глубокая проверка, что ac Здесь `actual` это проверяемое значение, а `expected` - то значение которое ожидается. -При сравнении `expected` и `actual` делается проверка соотвествия типов и значений. Если тип совпадает, и этот тип массив или объект, то делается не четкое сравнение значений, а их похожесть: все ключи и значения тождественно равны (`===`). Для применения того же правила к вложенным значением используется функция `assert.deep(expected, actual)`. +При сравнении `expected` и `actual` делается проверка соотвествия типов и значений. Если тип совпадает, и этот тип массив или объект, то делается нечеткое сравнение значений, то есть проверяется их похожесть: все ключи и значения тождественно равны (`===`). Для применения того же правила к вложенным значением используется функция `assert.deep(expected, actual)`. + +```js +assert([1, 2, 3], [1, 2, 3]); // ok +assert([{ foo: 1 }, { bar: 2 }], [{ foo: 1 }, { bar: 2 }]); // ошибка, разные объекты +assert.deep([{ foo: 1 }, { bar: 2 }], [{ foo: 1 }, { bar: 2 }]); // ok +``` -Если для проверки значения достаточно использовать операторы `===` или `==`, то такое выражение можно записать единственным аргументом `assert`. `Yatra` поймет, что левая часть это проверяемое значение, а правая - ответ. +Если для проверки значения достаточно использовать операторы `===` или `==`, то такое выражение рекомендуется записывать единственным аргументом `assert`. `Yatra` поймет, что левая часть это проверяемое значение, а правая - ответ. ```js assert(actual === expected); ``` -Если используются другие операторы, то просто проверяется истинность выражения. - -> Список таких операторов будет расширен. +![Сравнение](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/assert.png) -Ключевое отличие от других систем утверждений заключается в том, что если утверждение неверно, то тест продолжает выполняться. Таким образом, если в тесте использовано множество утверждений, то можно увидеть результат по всем, а не только по первому. Чаще всего это дает более полное представление о проблеме. +Если используются другие операторы, то проверяется только истинность выражения. ### Исключения -Если возникает исключение, то выполнение теста прекращается. В этом случае можно увидеть на какой строке возникла проблема: +Если в ходе выполнения теста возникает исключение, то выполнение теста прекращается. В этом случае будет показано на какой строке возникла проблема: ![Исключение](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/exception.png) -Иногда требуется проверить, что выполнение определенного кода должно приводит к исключению. В этом случае, такой код оборачивают в функцию и передают ее методу `assert.exception` или его синониму `assert.throws`. +Если исключение является ожидаемым и требуется проверить оно возникает, то такой код нужно обернуть в функцию и передать методу `assert.exception` или его синониму `assert.throws`. ```js -var foo = 123; +module.exports = { + name: 'Exception example', + test: function(done){ + var foo = 123; -assert.exception(function(){ - foo.exception(); -}); + assert.exception(function(){ + foo.exception(); // будет выброшено исключение, так как у чисел нет метод exception + }); + } +}; ``` -Если исключение будет выброшено, то утвержение будет считаться верным. Иначе будет считаться ошибкой. При возникновении исключения внутри функции обернутой `assert.exception` выполнение остального кода теста продолжается. +Если исключение будет выброшено, то утвержение будет считаться верным. Иначе будет считаться ошибкой. При возникновении исключений внутри функции обернутой `assert.exception` выполнение остального кода теста не прерывается. ### Асинхронные тесты Для написания асинхронных тестов, есть несколько возможностей. -Во-первых, в описании функции теста можно указать аргумент (его имя может быть любым), чьим его значением будет функция. Пока не будет вызвана эта функция, тест будет считаться выполняющимся. +Один из способов, указать в описании функции аргумент (его имя может быть любым), чьим значением будет функция. Тест не будет считаться завершенным, пока не будет вызвана эта функция. ```js module.exports = { @@ -209,20 +238,63 @@ module.exports = { Для выполнения асинхронных проверок не рекомендуется использовать `setTimeout` или `setInterval`. Для это нужно использовать метод `assert.async`, которому передается функция для выполнения в следующем фрейме. Внутри таких функций так же может быть вызван метод `assert.async`. Тест считается выполняющимся пока не выполнена хотя бы одна функция, заданная через `assert.async`. +```js +module.exports = { + name: 'Async test', + test: function(done){ + var foo = 1; + + // для примера, значение меняется через 50ms + setTimeout(function(){ + foo = 2; + }, 50); + + assert.async(function test(){ + if (foo !== 2) + assert.async(test); + + assert(foo === 2); + }); + } +}; +``` + Можно использоваит `assert.async` совместно с `done`. При этом тест будет считаться выполенным, когда выполнены все функции заданные через `assert.async` и выполнена функция `done`. Если в ходе выполнения кода теста, возникает исключение, то выполенение теста прекращается, а еще не выполненные функции, выставленные через `assert.async`, вызваны не будут. -Каждому тесту, если он выполняется асинхронно, отводится 250ms. Это значение можно изменить на уровне теста, задав свойство `timeout` с необходимым значением. Если по истечении этого времени все еще будут не выполнены часть функций, то +Каждому тесту, если он выполняется асинхронно, отводится 250ms. Это значение можно изменить на уровне теста, задав свойство `timeout` с необходимым значением. Если по истечении этого времени все еще будут не выполнены часть функций, то тест будет считаться заваленым. + +```js +module.exports = { + timeout: 1000, // увеличенный таймаут (по умолчанию 250ms) + name: 'Async test with custom timeout', + test: function(done){ + callSomeAsyncFunction(function(){ + done(); + }); + } +}; +``` ### Результат тестов -Тест считается пройденым, если все утверждения верны. +Тест считается пройденым, если все утверждения верны и тест не превысил максимально допустимое время. -Если тест не содержит утверждений, то он считается пропущенным (`pending`) вне зависимости от того какой код в нем выполняется. Также можно пропустить выполнение теста или пакет, задав свойство `pending` равным `true`. +Если тест не содержит утверждений, то он считается пропущенным (`pending`) вне зависимости от того какой код в нем выполняется. Можно намеренно пропустить выполнение теста или набора тестов, задав свойство `pending` равным `true`. + +```js +module.exports = { + pending: true, // пропустить тест + name: 'Pending test example', + test: function(done){ + console.log('Этот код не будет выполнен'); + } +}; +``` Набор тестов считается пройденым, если среди его тестов нет ни одного с ошибкой, и есть хотя бы один успешный. -Примеры различных ситуаций и варианты отображения, можно найти например в `example/showcase.html`. +Примеры различных ситуаций и варианты отображения можно посмотреть на примере `example/showcase.html`. ![Пример различных ситуаций](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/showcase.png) diff --git a/docs/img/assert.png b/docs/img/assert.png new file mode 100644 index 0000000..2834d1e Binary files /dev/null and b/docs/img/assert.png differ diff --git a/docs/img/several-assert.png b/docs/img/several-assert.png new file mode 100644 index 0000000..66f5fcf Binary files /dev/null and b/docs/img/several-assert.png differ