Кувалдин Артем, Жигалов Сергей
Храним заметки пользователя в виде файлов
GET / главная
GET /notes список заметок
POST /notes добавление заметки
GET /notes/todo-list просмотр заметки
POST /notes добавление заметки
{
createdAt: 1456983804639,
content: 'TODO list:
1. Проверить домашние задания
2. Рассказать лекцию про тесты
3. Не забыть выложить слайды'
}
Храним заметки пользователя в виде файлов
GET / главная
GET /notes список заметок
POST /notes добавление заметки
GET /notes/todo-list просмотр заметки
«На основании первой строки заметки сгенерировать человеко-понятный урл»
'TODO list:\n1. Проверить...' // 'todo-list'
function generateNoteId(str) {
return str
.split('\n').shift()
.toLowerCase()
.replace(/\s/g, '-');
}
module.exports = generateNoteId;
app/ └── src └── generateNoteId.js └── ... └── test └── generateNoteId-test.js └── ...
// generateNoteId-test.js
var generateNoteId = require('../src/generateNoteId');
var assert = require('assert');
describe('Note id generator', function () {
it('should cut first line', function () {
var actual = generateNoteId('first\nsecond');
assert.equal(actual, 'first');
});
it('should cast to lower case', function () {
var actual = generateNoteId('ToDo');
assert.equal(actual, 'todo');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('todo list');
assert.equal(actual, 'todo-list');
});
});
... передать не строку?
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/\s/g, '-');
}
... передать не строку?
... служебные символы?
... лишние пробелы?
...
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.replace(/[^a-z0-9\s]/g, '')
.toLowerCase()
.trim()
.replace(/\s+/g, '-');
}
⬇
Модули могут решать несколько задач
⬇
Сложнее в поддержке
⬇
Легче менять реализацию
⬇
Легче в поддержке
function generateNoteId(str) {
if (typeof str === 'string') {
str = str.split('\n').shift().toLowerCase();
return replaceSpaces(str);
}
}
function replaceSpaces(str)
return str
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
module.exports = generateNoteId;
«это процесс улучшения написанного ранее кода путем такого изменения его внутренней структуры, которое не влияет на внешнее поведение»
При рефакторинге сохраняется функциональность
Тесты проверяют функциональность
Тесты помогают рефакторить
Тесты помогают обновлять зависимости
Note id generator ✓ should cut first line ✓ should cast to lower case ✓ should replace spaces to `-` ✓ should return `undefined` when input data ... ✓ should exclude unknown symbols ✓ should exclude extra spaces ✓ should trim string - should translit 7 passing (17ms) 1 pending
«разработка через тестирование»
«разработка через тестирование поощряет простой дизайн и внушает уверенность»
Тест должен содержать одну логическую проверку которая не повторяется в других тестах*
[*] в идеале ;)
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return str
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
Note id generator ✓ should cut first line ✓ should cast to lower case ✓ should replace spaces to `-` ✓ should return `undefined` when input data ... ✓ should exclude unknown symbols ✓ should exclude extra spaces ✓ should trim string 7 passing (17ms)
it('should cast to lower case', function () {
// действие
var actual = generateNoteId('HellO');
// проверка
actual.should.be.equal('hello');
});
«это фреймворк для тестирования JavaScript приложений»
$ npm install mocha --save-dev
$ mocha generateNoteId-test.js
Note id generator ✓ should cast to lower case ✓ should replace spaces to `-` 2 passing (8ms)
{
"scripts": {
"test": "mocha test"
}
}
$ npm test
describe('Note id generator', function() {
it('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
describe > describe > ...
describe('Note id generator', function () {
describe('spaces', function () {
it('should replace to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
it('should exclude extra spaces', function () {
var actual = generateNoteId(' hel lo ');
actual.should.be.equal('hel-lo');
});
});
});
Note id generator spaces ✓ should replace to `-` ✓ should exclude extra spaces 2 passing (8ms)
Что тестируем?
describe('Note id generator', function () {
it('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
Что должно произойти?
describe('Note id generator', function () {
it('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
describe('My favorite module', function() {
before(function() {
// runs before all tests in this block
});
after(function() {
// runs after all tests in this block
});
beforeEach(function() {
// runs before each test in this block
});
afterEach(function() {
// runs after each test in this block
});
// ...
});
function createNote(createdAt, content){
return MongoClient
.connect(url)
.then(function (db) {
return db.collection('notes')
.insert({
createdAt,
content
});
});
};
before(function (done) {
MongoClient.connect(url)
.then(function (db) {
connect = db;
})
.then(done, done);
});
beforeEach(function (done) {
connect
.collection('notes')
.remove({}, () => done);
});
after(function (done) {
return connect.close();
});
it('should create note', function (done) {
createNote(123, 'myNote')
.then(function () {
return connect.collection('notes')
.find({}).toArray();
})
.then(function (actual) {
assert.equal(actual.length, 1);
assert.equal(actual[0].createdAt, 123);
assert.equal(actual[0].content, 'myNote');
})
.then(done, done);
});
? == true
// Success
assert(true);
assert.ok(true);
assert.ok('mu-ha-ha');
assert.ok({});
? == true
// Throw error
assert(false);
assert.ok(false);
assert.ok('');
assert.ok(0);
assert.ok(null);
? == ?
// Success
assert.equal(true, true);
assert.equal('xo', 'x' + 'o');
assert.equal('1', 1);
// Throw error
assert.equal({a: 1}, {a: 1});
? === ?
// Success
assert.strictEqual(true, true);
assert.strictEqual('xo', 'x' + 'o');
// Throw error
assert.strictEqual('1', 1);
assert.strictEqual({a: 1}, {a: 1});
// Success
assert.deepEqual(true, true);
assert.deepEqual('xo', 'x' + 'o');
assert.deepEqual('1', 1);
assert.deepEqual({a: 1}, {a: 1});
«Проверить что переменная notes является массивом»
notes instanceof Array // true
var notes = '';
assert.ok(notes instanceof Array);
1) Assert is array: AssertionError: false == true + expected - actual -false +true
var notes = '';
assert.ok(notes instanceof Array,
'notes is not array');
1) Assert is array: AssertionError: notes is not array + expected - actual -false +true
$ npm install chai --save-dev
require('chai').should();
var notes = '';
notes.should.be.an.instanceof(Array);
1) Assert is array:
AssertionError: expected '' to be an
instance of Array
describe('Note id generator', function () {
it.only('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
describe.only('Note id generator', function () {
it('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
describe('Note id generator', function () {
it.skip('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
describe.skip('Note id generator', function () {
it('should cast to lower case', function () {
var actual = generateNoteId('HellO');
assert.equal(actual, 'hello');
});
it('should replace spaces to `-`', function () {
var actual = generateNoteId('mu ha ha');
assert.equal(actual, 'mu-ha-ha');
});
});
$ mocha test -r spec
$ mocha test -r dot
$ mocha test -r min
$ mocha test -r nyan
«На основании первой строки заметки сгенерировать человеко-понятный урл латинскими символами»
var translit = require('translit');
function generateNoteId(str) {
if (typeof str !== 'string') {
return;
}
return translit(str)
.split('\n').shift()
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.trim()
.replace(/\s+/g, '-');
}
it('should translit russian characters', function () {
var actual = generateNoteId('привет');
actual.should.be.equal('privet');
});
$ npm install mockery --save-dev
var mockery = require('mockery');
it('should translit russian characters', function (){
function transliteMock (input) {
input.should.be.equal('Привет, мир!');
return 'Privet, mir!';
}
mockery.registerMock('translit', transliteMock);
mockery.enable();
generateNoteId = require('../src/generateNoteId');
var actual = generateNoteId('Привет, мир!');
actual.should.be.equal('privet-mir');
});
$ npm install sinon --save-dev
it('should translit russian characters', function () {
var stub = sinon.stub();
stub.withArgs('Привет, мир!')
.onFirstCall().returns('Privet, mir!');
stub.throws(Error('wrong translit argument'));
mockery.registerMock('translit', stub);
mockery.enable();
translit = require('../src/generateNoteId').translit;
var actual = translit('привет');
actual.should.be.equal('privet-mir');
});
«Студента можно подписать на событие, производимое преподавателем»
var getEmitter = require('./emitter');
var lecturer = getEmitter();
// subscribe daria to event `slide`
lecturer.on('slide', daria, function (){
console.log('Oho!');
});
// emit event `slide`
lecturer.emit('slide'); // Oho!
var sinon = require('sinon');
var emitter = require('../src/emitter')();
it('should call handler when', function () {
var spy = sinon.spy();
emitter.on('slide', daria, spy);
emitter.emit('slide');
spy.calledOnce.should.be.true;
});
var sinon = require('sinon');
var emitter = require('../src/emitter')();
it('should call handler when', function () {
var spy = sinon.spy(function () {
console.log('Mu-ha-ha!');
});
emitter.on('slide', daria, spy);
emitter.emit('slide'); // 'Mu-ha-ha!'
spy.calledOnce.should.be.true;
});
it('should not call handler', function () {
var spy = sinon.spy();
emitter.on('slide', daria, spy);
emitter.emit('funny');
spy.called.should.be.false;
});
it('should call handler twice', function () {
var spy = sinon.spy();
emitter.on('slide', daria, spy);
emitter.emit('slide');
emitter.emit('slide');
spy.callCount.should.equal(2);
});
it('should call handler with args', function () {
var spy = sinon.spy();
emitter.on('slide', daria, spy);
emitter.emit('slide', 'send data');
var firstCall = spy.getCall(0);
firstCall.args.length.should.equal(1);
firstCall.calledWith('send data').should.be.true;
});
it('should translate', function () {
return generateNoteId('привет')
.then(function (actual) {
actual.should.be.equal('hi');
});
});
it('should translate', function () {
nock('https://translate.yandex.net')
.get('/api/v1.5/tr.json/translate')
.query(true)
.reply(200, {text: ['Hello, world!']});
return translate('привет')
.then(function (actual) {
actual.should.be.equal('hello-world');
});
});
Получить данные о заметке, прейдя по урлу '/notes/:name'.
//routes.js
const pages = require('./controllers/pages');
const notes = require('./controllers/notes');
module.exports = function(app) {
app.post('/notes', notes.create);
app.get('/notes/:name', notes.item);
app.all('*', pages.error404)
};
const request = require('supertest');
const app = require('../app');
describe('Note page', function () {
it('should return note', function (done){
request(app)
.get('/notes/films')
.set('Cookie', 'top secret cookie')
.expect('Content-Type', 'text/html')
.expect(200, done)
});
it('should respond 404', function (done){
request(app)
.get('/notes/wrongNote')
.expect(404, done)
});
it('should create note', function (done){
request(app)
.post('/notes')
.send({...})
.expect(200, done)
.end(function (err, res) {
var body = res.body;
var expectedBody = {...};
body.should.deep.equal(expectedBody)
done(err);
});
});
});
«мера, которая показывает, на сколько исходный код в проекте был протестирован»
$ npm install -g istanbul
$ istanbul cover _mocha
$ open coverage/lcov-report/index.html