как я подсел на тесты

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

Яндекс

Это больно

Делают мой код лучше

Задача

На основании названия поста сгенерировать человеко-понятный урл (slug)

Пример


'Chat Bots for Telegram' // 'chat-bots-for-telegram'
'Tune in to Radio'       // 'tune-in-to-radio'
            

Решение до тестов


function slugGenerator(str) {
    return str
        .toLowerCase()
        .replace(/\s+/g, '-');
}
            

module.exports = slugGenerator;
            

// slugGenerator-test.js
var slugGenerator = require('../src/slugGenerator');
var assert = require('assert');

            

describe('Slug generator', function () {
    it('should cast to lower case', function () {
        var actual = slugGenerator('HellO');
        assert.equal(actual, 'hello');
    });
    it('should replace spaces to `-`', function () {
        var actual = slugGenerator('mu ha ha');
        assert.equal(actual, 'mu-ha-ha');
    });
});

mocha


$ npm install mocha --save-dev
            

$ mocha test
            

  Slug generator
     should cast to lower case
     should replace spaces to `-`

  2 passing (8ms)

А что если ...

... передать не строку?


function slugGenerator(str) {
    if (typeof str !== 'string') {
        return;
    }

    return str
        .toLowerCase()
        .replace(/\s+/g, '-');
}
            

А что если ...

... невалидные символы?


function slugGenerator(str) {
    if (typeof str !== 'string') {
        return;
    }
            

    return str
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, '')
        .replace(/\s+/g, '-');
}

А что если ...

... начинается с пробела?


function slugGenerator(str) {
    if (typeof str !== 'string') {
        return;
    }
            

    return str
        .toLowerCase()
        .replace(/[^a-z0-9\s]/g, '')
        .trim()
        .replace(/\s+/g, '-');
}

... под другим углом

Лучшая документация

  Comment delete
     should return 400 when commentId is invalid
     should return 404 when comment not found
     should return 403 when comment already removed
     should return 401 when user not authorized
     should return 403 when user is banned
     should return 403 when user not author
     should success remove comment
     should decrement commentsCount
     should decrement replyCount
    - should notify moderator

  9 passing (403ms)
  1 pending

Ускоряют поиск ошибок

  ...

  1049 passing (17s)
  1 failing

  1) Comment delete should return 400 when commentId is invalid:
     Error: expected 401 "Unauthorized", got 400 "Bad Request"
      at Test.assert (tests/v1/comment/comment-delete-test.js:205:15)
      ...

Проверить что переменная posts является массивом

posts instanceof Array // true
            

var posts = '';
assert.ok(posts instanceof Array);
            

1) Assert is array:

  AssertionError: false == true
  + expected - actual

  -false
  +true

var posts = '';
assert.ok(posts instanceof Array,
         'posts is not array');
            

1) Assert is array:

  AssertionError: posts is not array
  + expected - actual

  -false
  +true

chai


var chai = require('chai');
chai.should();
            

var posts = '';
posts.should.be.an.instanceof(Array);

1) Assert is array:

  AssertionError: expected '' to be an
                  instance of Array

Позволяют сделать больше

На основании названия поста на русском языке сгенерировать человеко-понятный урл (slug)

function slugGenerator(str) {
    if (typeof str !== 'string') {
        return;
    }
            
    return translate(str)
        .then(function (result) {
            return result
                .toLowerCase()
                .replace(/[^a-zа-яё0-9\s]/g, '')
                .trim()
                .replace(/\s+/g, '-');
        })

}

it('should translate', function (done) {
    slugGenerator('привет')
        .then(function (actual) {
            actual.should.be.equal('hi');
        })
        .then(done, done);
});
            

nock


it('should translate', function (done) {
    nock('https://translate.yandex.net')
        .get('/api/v1.5/tr.json/translate')
        .query(true)
        .reply(200, {text: ['mu-ha-ha']});
            
    translate('привет')
        .then(function (actual) {
            actual.should.be.equal('mu-ha-ha');
        })
        .then(done, done);
});

Обновлять зависимости

Держат команду в тонусе

actual


it('should cast to lower case', function () {
    var actual = slugGenerator('HellO');

    actual.should.be.equal('hello');
});

expected


it('should cast to lower case', function () {
    var actual = slugGenerator('HellO');

    var expected = 'hello';
    actual.should.be.equal(expected);
});

три части

  1. Подготовка
  2. Действие
  3. Проверка

it('should cast to lower case', function () {
    // действие
    var actual = slugGenerator('HellO');
            
    // проверка
    actual.should.be.equal('hello');
});

it('should find all posts', function () {
    // подготовка
    new Post({title: 'first'}).create();
    new Post({title: 'second'}).create();
            
    // действие
    var actual = Posts.findAll();
    // проверка
    actual.should.have.length(2);
});

Запускайте чаще

  • В IDE по хоткею
  • При сохранении файла
  • Перед коммитом и пушем
  • CI сервер

Travis

package.json


{
    "scripts": {
        "test": "mocha test"
    }
}
            

.travis.yml


language: node_js
node_js:
  - "4.1"
            

demo-time

Первый шаг

  • Хелпер
  • Базовая модель
  • Независимый модуль

Спасибо!

@sergey_zhigalov