# yatra

> Yatra – user friendly unit-test runner

Latest version **1.4.0** (published 2017-01-23) · MIT license · 0 weekly downloads

## Install

```sh
npm install yatra
pnpm add yatra
yarn add yatra
bun add yatra
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.4.0 |
| Published | 2017-01-23 |
| First published | 2015-02-02 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 5 |
| Author | Roman Dvornov |
| Maintainers | lahmatiy |
| Keywords | test, runner, javascript, testing, user-friendly |

## Links

- npm: https://www.npmjs.com/package/yatra
- Repository: https://github.com/basisjs/yatra
- Issues: https://github.com/basisjs/yatra/issues
- npm.io page: https://npm.io/package/yatra

## Alternatives

- [duck](https://npm.io/package/duck.md) — 4.2M weekly downloads
- [ava](https://npm.io/package/ava.md) — 560.2K weekly downloads
- [storybook-addon-module-mock](https://npm.io/package/storybook-addon-module-mock.md) — 71.7K weekly downloads
- [@ethereum-waffle/mock-contract](https://npm.io/package/@ethereum-waffle/mock-contract.md) — 40.0K weekly downloads
- [aws-elasticsearch-connector](https://npm.io/package/aws-elasticsearch-connector.md) — 37.4K weekly downloads

## Recent versions

- 1.4.0 (latest) — 2017-01-23
- 1.3.0 — 2016-10-08
- 1.2.3 — 2016-07-16
- 1.2.2 — 2016-07-16
- 1.2.1 — 2016-07-15
- 1.2.0 — 2016-07-13
- 1.1.0 — 2016-07-02
- 1.0.3 — 2015-12-24
- 1.0.2 — 2015-12-24
- 1.0.1 — 2015-05-13
- 1.0.0 — 2015-05-13
- 1.0.0-beta2 — 2015-02-03
- 1.0.0-beta1 — 2015-02-02

## README

[![NPM version](https://img.shields.io/npm/v/yatra.svg)](https://www.npmjs.com/package/yatra)

# Yatra

Yatra (Yet Another Test Runner) – "еще один" исполнитель модульных тестов (unit-test runner).

В основном `Yatra` расчитан на тестирование фреймворка [`basis.js`](https://github/basisjs/basisjs) и проектов, разрабатываемых с его применением.  Но это не является обязательным требованием и `Yatra` может использоваться в проектах и без `basis.js`.

Ключевыми особенностями являются удобная работа с тестами, атоматическое обновление тестов и их прогон при изменении тестируемого кода (не требуется самостоятельно обновлять страницу и перезапускать тесты), человекопонятная информация о результатах прохождения теста.

На данный момент, реализован не весь планируемый функционал. В будущем возможности будут расширяться (см. [TODO](todo)).

## Как использовать

`Yatra` поставляется в трех видах:

- `reporter` – как приложение;
- `lib` – как библиотека, для встраивания в другие интерфейсы;
- `runner` – непосредственно иполнитель тестов, не включает в себя интерфейс; предназначен для использования с другими тестирующими фреймворками и системами;

Приложение не экспортирует переменных в глобальную область видимости. `lib` и `runner` экспортируют единственную переменную `yatra`, предоставляющую интерфейс (интерфейсы различаются).

### Установка

Для установки сборки потребуется `npm`:

    > npm install yatra --save-dev

### Использование в исходном виде

Можно использовать `Yatra` и в исходном виде. Для этого необходимо клонировать репозитарий и установить зависимости:

    > git clone https://github.com/basisjs/yatra.git yatra
    > cd yatra
    > npm install

Основной файл интерфейса – `src/reporter.html`.

Сборка:

    > npm run build

Результат сборки окажется в папке `build`.

### Использование в проекте

Когда сборка `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`, выглядит это так:

![Как выглядит](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/example.png)

## TODO

- [x] придумать название :)
- [ ] полностью тестировать сам себя
- [ ] доделать стиль
- [ ] выводить в summary не только ошибки, но информацию об общем ходе выполнения тестов
- [x] beforeEach/afterEach (+done/async)
- [x] поддержка Promise
- [ ] возможность задавать порог, что тест медленно отрабатывает
- [x] возможность быстрой отладки
- [ ] возможность делать тесты производительности
- [ ] возможность использовать различные assert библиотеки
- [ ] добавить различные интерфейсы: bdd, tdd, exports, qunit
- [x] автоматизивароть сборку и зарегистрировать в bower
- [x] прогон тестов используя node.js (зарегистрировать в npm)
- [ ] поддержка code coverage
- [ ] интеграция с travis ci

## Описание тестов

Тесты описываются в виде вложенной структуры из объектов и массивов. Важными полями являются `name` и `test`. Первое задает подпись к тесту (название), а второе - либо сам тест, либо набор вложенных тестов.

```js
var myTestSuite = {
  name: 'Test suite',
  test: [
    {
      name: 'Test #1',
      test: function(){
        // test code
      }
    },
    {
      name: 'Test #2',
      test: function(){
        // test code
      }
    }
  ]
};
```

Если значением поля `test` является функция, то это непосредственно сам тест. Если массив, то набор тестов (`suite`).

Когда используется [`basis.js`](https://github.com/basisjs/basisjs), тесты и их наборы можно выносить в отдельные файлы и подключать используя функцию `require`. Это облегчает навигацию по тестам.

Обычно описывается корневой пакет тестов (`index.js`), который может выглядеть так:

```js
// test suite
module.exports = {
  name: 'Example test suite',
  html: __dirname + 'env.html', // базовый файл окружения
  test: [
    require('./spec/suite1.js'),
    require('./spec/suite2.js'),
    require('./spec/suite3.js'),
    require('./spec/suite4.js')
  ]
};
```

Этот файл подключает другие файлы, являясь своего рода входной точкой. Наборы тестов описываются в отдельных файлах и, обычно, располагаются в папке `spec`.

Свойство `html` задает файл, который будет использоваться для задания окружения выполняемым тестам. Такой файл загружается в `iframe`, и код тестов выполняется в рамках этого фрейма. Значение свойства `html` наследуется вложенными тестами. При необходимости его можно переопределить для определенного теста или поддерева тестов.

Типовой набор тестов (`suite`):

```js
module.exports = {
  name: 'Test suite',
  init: function(){
    // ...
  },
  test: [
    // тесты
  ]
};
```

Свойство `init` позволяет задать функцию инициализирующее окружение, является необязательным. Этот код будет выполнен один единственный раз в момент инициализации, перед выполнением первого теста в поддереве тестов. Стоит иметь ввиду, что код этой функции выполняется в отдельном окружении (в рамках `iframe`), и потому у него будет область видимости отличная от той, в которой описывается сама функция. То же касается и самих тестов.

Переменные объявленные в `init` будут доступны всем тестам. Упрощенный код как это работает:

```js
// выбираем код тела функции init и выполняем его в текущем окружении
eval(getFunctionBody(testSuite.init));

// функция которая исполняет код тестов
function runTest(code){
  // выполняется код тела функции, но сама функция не вызывается
  eval(getFunctionBody(code));
}

// тесты выполняются один за другим
runTest(test1);
runTest(test2);
// ...
```

Если у теста (или набора) есть свойство `html` или `init`, то для него и его вложенных тестов создается собственное окружение (отдельный `iframe`).

### Утверждения

В тестах выполняемый код должен сопровождаться утверждениями (`assertions`). Ключевое отличие от других систем утверждений заключается в том, что если утверждение неверно, то тест продолжает выполняться. Таким образом, если в тесте использовано множество утверждений, то можно увидеть результат по всем, а не только по первому. Чаще всего это дает более полное представление о проблеме.

![Несколько неверных утверждений](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/several-assert.png)

Для проверки утверждения используется функция `assert`, которая доступна как локальная переменная. Функция может принимать один или два два аргумента:

```js
assert(actual);                  // проверяется, что значение правдиво, то есть
                                 // не равно '' (пустая строка), null, undefined, false, 0 и NaN
assert(expected, actual);        // проверяется, что actual равно expected
assert.deep(expected, actual);   // глубокая проверка, что actual равно expected
```

Здесь `actual` это проверяемое значение, а `expected` - то значение которое ожидается.

При сравнении `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` поймет, что левая часть это проверяемое значение, а правая - ответ.

```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()`.

```js
module.exports = {
  name: 'Exception example',
  test: function(done){
    var foo = 123;

    assert.exception(function(){
      foo.exception();  // будет выброшено исключение, так как у чисел нет метод exception
    });
  }
};
```

Если исключение будет выброшено, то утвержение будет считаться верным. Иначе будет считаться ошибкой. При возникновении исключений внутри функции обернутой `assert.exception()` выполнение остального кода теста не прерывается. `assert.exception()` является укороченной записью для следующего кода:

```js
// тестирование ожидаемого исключения без assert.exception()
module.exports = {
  name: 'Exception example',
  test: function(done){
    var foo = 123;
    var throwsException = false;

    try {
      foo.exception();  // будет выброшено исключение, так как у чисел нет метод exception
    } catch(e) {
      throwsException = true;
    }

    assert(throwsException === true);
  }
};
```

### Асинхронные тесты

Для написания асинхронных тестов, есть несколько возможностей.

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

```js
module.exports = {
  name: 'Async test',
  test: function(done){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      if (xhr.readyState == 4)
      {
        assert(xhr.status === 200);
        done();
      }
    };
    xhr.open('GET', 'file.txt', true);
    xhr.send();
  }
};
```

Для выполнения асинхронных проверок не рекомендуется использовать `setTimeout` или `setInterval`. Для это нужно использовать метод `assert.async`, которому передается функция для выполнения в следующем фрейме. Внутри таких функций так же может быть вызван метод `assert.async`. Тест считается выполняющимся пока не выполнена хотя бы одна функция, заданная через `assert.async`.

```js
module.exports = {
  name: 'Async test',
  test: function(){
    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` с необходимым значением. Если по истечении этого времени все еще будут не выполнены часть функций, то тест будет считаться заваленым.

```js
module.exports = {
  timeout: 1000,  // увеличенный таймаут (по умолчанию 250ms)
  name: 'Async test with custom timeout',
  test: function(done){
    callSomeAsyncFunction(function(){
      done();
    });
  }
};
```

Если функция теста возвращает `Promise` (значение с методом `then`), то тест будет считаться завершенным, когда разрешится `Promise`. Как будет разрешен `Promise` – `resolve` или `reject` – не влияет на результат теста.

```js
module.exports = {
  name: 'Await promise is resolved',
  test: function(){
    return new Promise(function(resolve, reject){
      setTimeout(function(){
        resolve();
      }, 10);
    });
  }
};
```

### Визитер

В некоторых тестах необходимо определять, что были пройдены определенные точки кода, а некоторые не были. Для этого можно использовать специальную функцию `visit()` для запоминания пройденных точек и метод `assert.visited()` для проверки.

```js
module.exports = {
  name: 'Visited points',
  test: function(){
    visit('foo');
    visit({ bar: true });

    assert.visited(['foo', { bar: true }]);
  }
};
```

Метод `visit.list()` позволяет получить текущий копию списка точек, а `visit.reset()` очищает этот список.

```js
module.exports = {
  name: 'visit.list() and visit.reset()',
  test: function(){
    visit({ foo: 'test' });
    assert.deep([{ foo: 'test' }], visit.list()); // эквивалентно assert.visited({ foo: 'test' });

    visit.reset();
    assert.deep([], visit.list());
  }
};
```

Для того, чтобы отметить некоторую точку, что она не должна посещаться используется метод `visit.wrong()`.

```js
module.exports = {
  name: 'visit.wrong()',
  test: function(){
    visit.wrong(); // эффект тот же, что и assert(false), но в данном случае
                   // будет выведено более говорящее сообщение
  }
};
```

Метод `visit.wrap()` позволяет обернуть метод, чтобы записывать его вызовы. В список точек будет сохраняться первый аргумент:

```js
module.exports = {
  name: 'visit.wrap()',
  test: function(){
    var obj = {
      foo: function(obj){
        return obj;
      }
    };

    visit.wrap(obj, 'foo'); // перед выполнением метода в список точек сохранится первый аргумент

    obj.foo(1, 2);
    obj.foo({ bar: 1 });

    assert.visited([1, { bar: 1 }]);
  }
};
```

### Результат тестов

Тест считается пройденым, если все утверждения верны и тест не превысил максимально допустимое время.

Если тест не содержит утверждений, то он считается пропущенным (`pending`) вне зависимости от того какой код в нем выполняется. Можно намеренно пропустить выполнение теста или набора тестов, задав свойство `pending` равным `true`.

```js
module.exports = {
  pending: true,  // пропустить тест
  name: 'Pending test example',
  test: function(done){
    console.log('Этот код не будет выполнен');
  }
};
```

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

Примеры различных ситуаций и варианты отображения можно посмотреть на примере `example/showcase.html`.

![Пример различных ситуаций](https://raw.githubusercontent.com/basisjs/test-runner/master/docs/img/showcase.png)

---
_Source: https://npm.io/package/yatra · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
