Автотесты

Assert. Unit tests. Mocha

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

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

Задача

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

Задача. Пример


const forms = ['монета', 'монеты', 'монет'];

getPlural(1, forms); // 'монета'
getPlural(2, 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];
}
        

Задача. Проверяем


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(value)


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

assert success


const assert = require('assert');
const forms = ['монета', 'монеты', 'монет'];

assert(getPlural(1, forms) === 'монета');
        

        

assert error


const assert = require('assert');
const forms = ['монета', 'монеты', 'монет'];

assert(getPlural(1, forms) === 'монет');
        

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

assert message


const assert = require('assert');
const forms = ['монета', 'монеты', 'монет'];

assert(
    getPlural(1, forms) === 'монета',
    'Должен вернуть `монета` если передать `1`');
        

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

assert equal


const assert = require('assert');
const forms = ['монета', 'монеты', 'монет'];

assert.equal(getPlural(1, forms), 'монет');
        

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: 'монета' == 'монет'
    at Object.<anonymous> (/plural.js:17:8)
        

assert итог

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

Задача. Проверяем


const assert = require('assert');
const forms = ['монета', 'монеты', 'монет'];

assert.equal(getPlural(1, forms), 'монета');
assert.equal(getPlural(2, forms), 'монеты');
assert.equal(getPlural(5, 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. Для 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) {
    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];
}
        
Я ответственный разработчик. Думаю прежде чем писать код.

Тесты?

No

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

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

mocha установка


npm install mocha --save-dev
        

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

mocha подготовка


// 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`');
});
        

mocha запуск


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
        

mocha тест


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

describe('getPlural', () => {
    const forms = ['монета', 'монеты', 'монет'];

    it('should return `монета` for `1`', () => {
        const actual = getPlural(1, forms);

        assert.equal(actual, 'монета');
    });

    // ...
});
        

mocha запуск


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
        

mocha запуск


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)
        

mocha запуск


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

npm test
        

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

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


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/);
});
        

Асинхронный код

Асинхронный код. Задача

Узнать из файла `package.json` список всех зависимостей, которые нужны во время разработки.

Асинхронный код. Пример


{
  "name": "05-unit-test",
  "scripts": {
    "test": "mocha tests"
  },
  "devDependencies": {
    "mocha": "3.2.0"
  }
}
        

Асинхронный код. Подготовка


const fs = require('fs');

function readFile(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf-8', (error, data) => {
            if(error) {
                reject(error);
            } else {
                resolve(data);
            }
        });
    });
}
    

Асинхронный код. Решение


function getDevDependencies(path) {
    return readFile(path)
        .then(JSON.parse)
        .then(data => data.devDependencies);
}
        

Асинхронный код. Тесты


it('should return devDependencies', done => {
    const path = __dirname + '/../package.json';

    getDevDependencies(path)
        .then(actual => {
            const expected = { mocha: '3.2.0' };

            assert.deepEqual(actual, expected);
            done();
        })
        .catch(error => done(error));
});
        

Асинхронный код. Тесты


it('should return devDependencies', () => {
    const path = __dirname + '/../package.json';

    return getDevDependencies(path)
        .then(actual => {
            const expected = { mocha: '3.2.0' };

            assert.deepEqual(actual, expected);
        });
});
        

hook

hook beforeEach


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

    // ...
});
        

hook

  • beforeEach
  • afterEach
  • before
  • after

hooks. Пример


describe('getDevDependencies', () => {
    const path = __dirname + '/package.json';

    before(done => {
        const data = '{"devDependencies": {"mocha": "1.2.3"}}';

        fs.writeFile(path, data, done);
    });

    after(done => {
        fs.unlink(path, done);
    });

    // ...
});
        

Вложенность

Вложенность


describe('getDevDependencies', () => {
    before(() => { /* ... */ });

    after(() => { /* ... */ });

    it('should read data', () => { /* ... */ });
});
        

Вложенность


describe('package helper', () => {
    describe('getDevDependencies', () => {
        before(() => { /* ... */ });

        after(() => { /* ... */ });

        it('should read data', () => { /* ... */ });
    });
});
        

Вложенность


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 something', () => console.log(5));
        it('should do other', () => console.log(6));
    });
});
        

Вложенность


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

mocha. only


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

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

Module name
first
  ✓ should do something


1 passing (7ms)
        

mocha. skip


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

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

Module name
first
  ✓ should do something
  - 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

Итого

  • Assert
  • Юнит тесты
  • mocha

Почитать