Автотесты

Assert. Mocha. Организация тестов

Жигалов Сергей

«До окончания операции осталось 41 секунд»

Задача

Выбрать правильную форму склонения числительного

Пример


const forms = ['секунда', 'секунды', 'секунд'];

forms[0]; // 'секунда'
forms[1]; // 'секунды'
forms[2]; // 'секунд'
                

Пример


function getPlural(count, forms) {
    /* ... */
}

const forms = ['секунда', 'секунды', 'секунд'];
getPlural(21, forms); // 'секунда'
getPlural(62, forms); // 'секунды'
getPlural(5, forms);  // 'секунд'
                

Наблюдения


const forms = ['секунда', 'секунды', 'секунд'];
                
  • Заканчивается на 1, то forms[0]
  • Заканчивается на 2 - 4, то forms[1]
  • Заканчивается на 0, 5 - 9, то forms[2]
  • Заканчивается на 11 - 14, то forms[2]

Алгоритм

  1. Для 11-14 вернуть forms[2]
  2. Взять остаток от деления на 10
  3. Для 1 вернуть forms[0]
  4. Для 2-4 вернуть forms[1]
  5. Для 0, 5-9 вернуть forms[2]

Код


function getPlural(count, forms) {
    if (count > 10 && count < 15)
        return forms[2];

    const residue = count % 10;

    if (residue === 1)
        return forms[0];
    if (residue > 0 && residue < 5)
        return forms[1];
    return forms[2];
}
                    

// Для 11-14
// вернуть forms[2]

// отстаток от % 10

// Для 1
// вернуть forms[0]
// Для 2-4
// вернуть forms[1]
// Иначе forms[2]
                    

Проверяем


const forms = ['секунда', 'секунды', 'секунд'];

getPlural(1, forms);  // 'секунда'
getPlural(2, forms);  // 'секунды'
getPlural(5, forms);  // 'секунд'
getPlural(11, forms); // 'секунд'
getPlural(22, forms); // 'секунды'
                

Проверяем


const forms = ['секунда', 'секунды', 'секунд'];

getPlural(1, forms) === 'секунда';  // true
getPlural(2, forms) === 'секунды';  // true
getPlural(5, forms) === 'секунд';   // true
getPlural(11, forms) === 'секунд';  // true
getPlural(22, forms) === 'секунды'; // true
                

assert

Своя реализация


function assert(value) {
    const hasError = !value;
    if (hasError) {
        throw new Error('Assertion failed');
    }
}
                

throw


console.log(1);
throw new Error('Mu-ha-ha!');
console.log(2);
                

1
/path/to/file.js:2
throw new Error('Mu-ha-ha!');
^

Error: Mu-ha-ha!
    at Object. (/path/to/file.js:2:7)
                

Готовая реализация


const assert = require('assert');
                

success


const assert = require('assert');
const forms = ['секунда', 'секунды', 'секунд'];

const actual = getPlural(1, forms);
const expected = 'секунда';

assert(actual === expected);
                

>
                

error


const assert = require('assert');
const forms = ['секунда', 'секунды', 'секунд'];

const actual = getPlural(1, forms);
const expected = 'секунд';

assert(actual === expected);
        

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: false == true
    at Object.<anonymous> (/plural.js:17:1)
        

message


const assert = require('assert');
const forms = ['секунда', 'секунды', 'секунд'];

const actual = getPlural(1, forms);
const expected = 'секунд';

assert(actual === expected,
      'Должна вернуться`секунда`,' +
      'если передать `1`');
        

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: Должна вернуться`секунда`, если передать `1`
    at Object.<anonymous> (/plural.js:17:1)
        

equal


const assert = require('assert');
const forms = ['секунда', 'секунды', 'секунд'];

const actual = getPlural(1, forms);
const expected = 'секунда';

assert.equal(actual, expected);
                

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: 'секунда' == 'секунд'
    at Object.<anonymous> (/plural.js:17:8)
                

strictEqual


const assert = require('assert');

const actual = '1';
const expected = 1;

assert.strictEqual(actual, expected);
                

assert.js:42
  throw new errors.AssertionError({
  ^

AssertionError [ERR_ASSERTION]: '1' === 1
    at Object.<anonymous> (/path/to/file.js:6:8)
                

deepEqual


const assert = require('assert');

const actual = ['секунда'];
const expected = ['секунда'];

assert.equal(actual, expected);
                

assert.js:42
  throw new errors.AssertionError({
  ^

AssertionError [ERR_ASSERTION]: [ 'секунда' ] == [ 'секунда' ]
    at Object.<anonymous> (/path/to/file.js:6:8)
                

deepEqual


const assert = require('assert');

const actual = ['секунда'];
const expected = ['секунда'];

assert.deepEqual(actual, expected);
                

>
                

assert.deepEqual
assert.deepStrictEqual
assert.doesNotThrow
assert.equal
assert.fail
assert.fail
assert.ifError
                    

assert.notDeepEqual
assert.notDeepStrictEqual
assert.notEqual
assert.notStrictEqual
assert.ok
assert.strictEqual
assert.throws
                    

итог

  • Проверяет данные
  • Локализует ошибки
  • Используется в коде
  • Никак не влияет в случае успеха

Проверяем


const assert = require('assert');
const forms = ['секунда', 'секунды', 'секунд'];

assert.equal(getPlural(1, forms), 'секунда');
assert.equal(getPlural(2, forms), 'секунды');
assert.equal(getPlural(11, forms), 'секунд');
assert.equal(getPlural(22, forms), 'секунды');
                

assert.equal(getPlural(113, forms), 'секунд');
                

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: 'секунды' == 'секунд'
    at Object.<anonymous> (/plural.js:22:8)
        

Алгоритм

  1. Взять остаток от деления на 100
  2. Для 11-14 вернуть forms[2]
  3. Взять остаток от деления на 10
  4. Для 1 вернуть forms[0]
  5. Для 2-4 вернуть forms[1]
  6. Для 0, 5-9 вернуть forms[2]

Исправляем


function getPlural(count, forms) {
    count %= 100;
    if (count > 10 && count < 15)
        return forms[2];
    const residue = count % 10;
    if (residue === 1)
        return forms[0];
    if (residue > 0 && residue < 5)
        return forms[1];
    return forms[2];
}
                    

// отстаток % 100
// Для 11-14
// вернуть forms[2]
// отстаток % 10
// Для 1
// вернуть forms[0]
// Для 2-4
// вернуть forms[1]
// Иначе forms[2]
                    

Автотест

(автоматизированный тест) – это скрипт, имитирующий взаимодействия пользователя с приложением, цель которого – локализация ошибок в работе программного обеспечения.
Я - ответственный разработчик. Думаю перед тем как писать код.

Тесты?

No

Для чего нужны тесты?

  • В коде нет ошибок
  • Вносить изменения
  • Рефакторить
  • Живая документация
  • Локализация ошибки
  • Структура кода

Организация тестов

Фреймворки

установка


npm install mocha --save-dev
                

// package.json
{
    "devDependencies": {
        "mocha": "5.0.5"
    }
}
                

Структура файлов



├── node_modules
├── package.json
├─┬ lib
│ └── plural.js
└─┬ tests
  └── plural-test.js
                

подготовка


// tests/plural-test.js

describe('getPlural', () => {
    it('should return `секунда` for `1`');
    it('should return `секунды` for `2`');
    it('should return `секунд` for `5`');
    it('should return `секунд` for `11`');
    it('should return `секунды` for `22`');
    it('should return `секунд` for `113`');
});
                

BDD

(Behavior-driven development) описание пользовательских сценариев на естественном языке.

подготовка


// tests/plural-test.js

describe('getPlural', () => {
    it('should return `секунда` for `1`');
    it('should return `секунды` for `2`');
    it('should return `секунд` for `5`');
    it('should return `секунд` for `11`');
    it('should return `секунды` for `22`');
    it('should return `секунд` for `113`');
});
                

запуск


node_modules/.bin/mocha tests
                

getPlural
  - should return `секунда` for `1`
  - should return `секунды` for `2`
  - should return `секунд` for `5`
  - should return `секунд` for `11`
  - should return `секунды` for `22`
  - should return `секунд` for `113`


0 passing (6ms)
6 pending
                

const assert = require('assert');

describe('Calculator', () => {
    it('should sum digits', () => {
        assert.equal(1 + 1, 2);
    });
});
                

  Calculator
    ✓ should sum digits


  1 passing (6ms)
                

const assert = require('assert');

describe('Calculator', () => {
    it('should multiple digits', () => {
        assert.equal(1 * 1, 2);
    });
});
                

  Calculator
    1) should multiple digits

  0 passing (9ms)
  1 failing

  1) Calculator
       should multiple digits:

      AssertionError [ERR_ASSERTION]: 1 == 2
      + expected - actual

      -1
      +2

      at Context.it (tests/example-test.js:5:16)
                

тест


// tests/plural-test.js
const getPlural = require('../lib/plural.js')
const assert = require('assert');

describe('getPlural', () => {
    const forms = ['секунда', 'секунды', 'секунд'];

    it('should return `секунда` for `1`', () => {
        const actual = getPlural(1, forms);

        assert.equal(actual, 'секунда');
    });

    // ...
});
                

запуск


node_modules/.bin/mocha tests
        

getPlural
  ✓ should return `секунда` for `1`
  - should return `секунды` for `2`
  - should return `секунд` for `5`
  - should return `секунд` for `11`
  - should return `секунды` for `22`
  - should return `секунд` for `113`


1 passing (7ms)
5 pending
                

запуск


node_modules/.bin/mocha tests
                

getPlural
  ✓ should return `секунда` for `1`
  ✓ should return `секунды` for `2`
  ✓ should return `секунд` for `5`
  ✓ should return `секунд` for `11`
  ✓ should return `секунды` for `22`
  ✓ should return `секунд` for `113`


6 passing (9ms)
                

Проверка исключений

Пример


function getPlural(count, forms) {
    if (isNaN(count)) {
        throw new Error('Count is not a number');
    }

    // ...
}
                

Тест


it('should throw error when count is not a number', () => {
    try {
        getPlural('NaN', forms);
        throw new Error('`getPlural` should throw error')
    } catch (error) {
        assert.equal(error.message, 'Count is not a number');
    }
});
                

Тест


it('should throw error when count is not a number', () => {
    const cb = () => getPlural('NaN', forms);

    assert.throws(cb, /Count is not a number/);
});
                

Вложенность

Вложенность


describe('getPlural', () => {
    it('should return `секунда` for `1`');
    it('should return `секунды` for `2`');
    it('should return `секунд` for `5`');
    it('should return `секунд` for `11`');
    it('should return `секунды` for `22`');
    it('should return `секунд` for `113`');
    it('should throw error when ...');
});
                

Вложенность


describe('getPlural', () => {
    describe('positive', () => {
        it('should return `секунда` for `1`');
        it('should return `секунды` for `2`');
        describe('third form', () => {
            it('should return `секунд` for `5`');
            it('should return `секунд` for `11`');
            it('should return `секунд` for `113`');
        });
    });
    describe('negative', () => {
        it('should throw error when ...');
    });
});
                

hook

beforeEach


describe('hooks', () => {
    beforeEach(() => {
        // Код этой функции выполнится
        // перед каждым тестом
    });

    // ...
});
        

hook

  • beforeEach
  • afterEach
  • before
  • after

Пример


describe('Module name', () => {
    before(() => console.log(1));
    beforeEach(() => console.log(2));

    describe('method name', () => {
        before(() => console.log(3));
        beforeEach(() => console.log(4));

        it('should do ...', () => console.log(5));
        it('should do other', () => console.log(6));
    });
});
                

Пример


Module name
1
  method name
3
2
4
5
    ✓ should do ...
2
4
6
    ✓ should do other
        

only


describe('Module name', () => {
    it.only('should do ...', () => {
        console.log('first');
    });

    it('should do other', () => {
        console.log('second');
    });
});
        

Module name
first
  ✓ should do ...


1 passing (7ms)
        

only


describe.only('Module name', () => {
    it('should do ...', () => {
        console.log('first');
    });

    it('should do other', () => {
        console.log('second');
    });
});
        

Module name
first
  ✓ should do ...
  ✓ should do other


2 passing (7ms)
        

skip


describe('Module name', () => {
    it('should do ...', () => {
        console.log('first');
    });

    it.skip('should do other', () => {
        console.log('second');
    });
});
        

Module name
first
  ✓ should do ...
  - should do other

1 passing (7ms)
1 pending
        

Отчеты

Отчеты SPEC

spec

Отчеты DOT MATRIX

dot

Отчеты NYAN

nyan

Отчеты TAP

tap

Отчеты LANDING STRIP

landing

Отчеты LIST

list

Отчеты PROGRESS

progress

Отчеты JSON

json

Отчеты JSON STREAM

json-stream

Отчеты MIN

min

Отчеты DOC

doc

Отчеты HTML

html

запуск


// package.json
{
    "scripts": {
        "test": "mocha tests"
    }
}
                

npm test
                

Итого

  • Assert
  • mocha icomocha
  • Организация тестов
  • Hook
  • Отчёты
  • Запуск

Почитать

Домашняя работа