Starting TDD with Node.js

9a3693c0c3d12f2fbae51792dd6a1344?s=47 Akito0107
November 12, 2016

Starting TDD with Node.js

Node学園祭2016でのLT資料です。

9a3693c0c3d12f2fbae51792dd6a1344?s=128

Akito0107

November 12, 2016
Tweet

Transcript

  1. Node.jsͰ࢝ΊΔ
 TDD Akito Ito

  2. ࣗݾ঺հ • ҏ౻ɹӯɹ(͍ͱ͏ ͖͋ͱ) • ϦΫϧʔτςΫϊϩδʔζͱ͍͏ձࣾͰ
 Node.js੡γεςϜͷӡ༻։ൃΛ͍ͯ͠·͢ • @Akito0107

  3. ES2015 / ES2016 / ES2017 • Node.js (ͱv8) ͷαϙʔτ͕ͲΜͲΜਐΜͰ ͍Δ

    • Node6͸ES2015 99%αϙʔτ • Node4 (1೥લ)͸57%…. • ͜ͷྲྀΕʹ৐Γ஗Εͨ͘ͳ͍
  4. ΧδϡΞϧʹม׵ • e.g. ES2015෩ʹॻ͖௚͢ function plzRefactorToArrow() { return { value:

    0, increment: function() { ++this.value; return this.value; } } }
  5. ΧδϡΞϧʹม׵ • e.g. ES2015෩ʹॻ͖௚͢ function arrow() { return { value:

    0, increment: () => { ++this.value; return this.value; } } }
  6. ΧδϡΞϧʹม׵ • e.g. ES2015෩ʹॻ͖௚͢ function arrow() { return { value:

    0, increment: () => { ++this.value; return this.value; } } }
  7. None
  8. ݱ࣮͸ݫ͍͠ • ՔಇதͷαʔϏεΛES2015 / 2016ʹఴͬͯ
 ॻ͖௚͢ͷ͸؆୯Ͱ͸ͳ͍ɻ • ݴ͍Α͏ͷͳ͍ෆ҆ͱͷઓ͍ • ͱ͸͍͑ɺ৽͍͠ػೳ΋࢖͍͖͍ͬͯͨ

  9. ݱ࣮͸ݫ͍͠ • ՔಇதͷαʔϏεΛES2015 / 2016ʹఴͬͯ
 ॻ͖௚͢ͷ͸؆୯Ͱ͸ͳ͍ɻ • ݴ͍Α͏ͷͳ͍ෆ҆ͱͷઓ͍ • ͱ͸͍͑ɺ৽͍͠ػೳ΋࢖͍͖͍ͬͯͨ

    TDD
  10. What is TDD ?? • Test Driven Development 
 (ςετۦಈ։ൃ)

    • ։ൃର৅ͷػೳʹରͯ͠ҎԼͷϓϩηεΛ܁Γฦ͢;
 1. ςετΛॻ͖ɺfailͤ͞Δ
 2. ػೳΛ࣮૷͠ɺςετΛpassͤ͞Δ
 3. ϦϑΝΫλ
  11. TDD Cycle UPEPMJTUͷ࡞੒ ςετ࡞੒ 3FE ؀ڥઃఆɹ ػೳ࣮૷ (SFFO ϦϑΝΫλ

  12. 0. ؀ڥઃఆ • Testing framework / test runner • mocha

    / ava / eater / etc… • ςετࣗಈԽπʔϧ΋ඞਢ • ϑΝΠϧมߋΛݕ஌ͯࣗ͠ಈͰtestΛճ͢ • npm-watch / mocha -w / etc…
  13. 1. TodoϦετͷ࡞੒ • ࠓ͔Β࣮૷͢ΔػೳϦετ • ࢓༷ͳͲ͕൑໌͍ͯ͠Δ৔߹͸ͦΕ΋
 ͔ͬ͠Γॻ͘ • ࣮૷ͷॱ൪Λ༏ઌॱҐ΍TestabilityʹΑͬͯ
 ܾఆ͢Δ

    • ஍ຯʹେࣄͳͷͰαϘΒͣʹॻ͜͏
  14. Example: todo list • (expressͷshopping applicationΛ૝ఆ) • API GET /items

    • response bodyʹ ‘items’ property͕͋Δ • items͸DB͔ΒऔಘͰ͖Δʢࠓճ͸mock) • pagination (limit: 30)
  15. 2. Write a test • Todo list͔Β1ͭػೳΛpopͯ͠ɺ࠷ॳʹςε τΛॻ͘ • ඞͣςετ͕མͪΔ͜ͱΛ֬ೝ͢Δʂ

  16. ࠷ॳͷExpressϋϯυϥ var express = require('express'); var request = require('supertest'); var

    assert = require('power-assert'); function index(req, res) { res.send(); } describe('GET /items', function() { it('response body contains items', function(done) { var app = express(); app.get('/items', index); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); });
  17. ࠷ॳͷExpressϋϯυϥ var express = require('express'); var request = require('supertest'); var

    assert = require('power-assert'); function index(req, res) { res.send(); } describe('GET /items', function() { it('response body contains items', function(done) { var app = express(); app.get('/items', index); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); });
  18. ࠷ॳͷExpressϋϯυϥ var express = require('express'); var request = require('supertest'); var

    assert = require('power-assert'); function index(req, res) { res.send(); } describe('GET /items', function() { it('response body contains items', function(done) { var app = express(); app.get('/items', index); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); });
  19. ࠷ॳͷExpressϋϯυϥ var express = require('express'); var request = require('supertest'); var

    assert = require('power-assert'); function index(req, res) { res.send(); } describe('GET /items', function() { it('response body contains items', function(done) { var app = express(); app.get('/items', index); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); });
  20. Fail (RED)

  21. 3. Production Codeͷ࣮૷ • Test͕Pass͢Δ࠷খݶͷίʔυΛॻ͘ • (͜ͷஈ֊Ͱ͸)ՄಡੑͳͲ͸ߟྀ͠ͳ͍

  22. 3. Production Codeͷ࣮૷ function index(req, res) { res.send({ items: []

    }); } describe('GET /items', function() { it('response body contains items', function(done) { var app = express(); app.get('/items', index); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); });
  23. 3. Production Codeͷ࣮૷ function index(req, res) { res.send({ items: []

    }); } describe('GET /items', function() { it('response body contains items', function(done) { var app = express(); app.get('/items', index); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); });
  24. Pass (Green)

  25. TDD Cycle UPEPMJTUͷ࡞੒ ςετ࡞੒ 3FE ؀ڥઃఆɹ ػೳ࣮૷ (SFFO ϦϑΝΫλ 2ͱ3Λ܁Γฦ͢

  26. Write a Test function index(req, res) { res.send({ items: []

    }); } describe('GET /items', function() { . . . . . . . . . it('Get Items from DB', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }]; var app = express(); app.get('/items', index); request(app) .get('/items') .expect({items: items}, done); }); });
  27. Write a Test function index(req, res) { res.send({ items: []

    }); } describe('GET /items', function() { . . . . . . . . . it('Get Items from DB', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }]; var app = express(); app.get('/items', index); request(app) .get('/items') .expect({items: items}, done); }); });
  28. Write a Test function index(req, res) { res.send({ items: []

    }); } describe('GET /items', function() { . . . . . . . . . it('Get Items from DB', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }]; var app = express(); app.get('/items', index); request(app) .get('/items') .expect({items: items}, done); }); });
  29. Write a Test function index(req, res) { res.send({ items: []

    }); } describe('GET /items', function() { . . . . . . . . . it('Get Items from DB', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }]; var app = express(); app.get('/items', index); request(app) .get('/items') .expect({items: items}, done); }); });
  30. Red

  31. Implement function index(options) { var Item = options.Item return function(req,

    res) { Item.find({}, function(err, docs) { res.send({ items: docs }); }); } } ..................... it('Get items', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }] var Item = { find: function(opts, cb) { setImmediate(function() { cb(null, items); }); } }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect({ items: items }, done); }); .....................
  32. Implement function index(options) { var Item = options.Item return function(req,

    res) { Item.find({}, function(err, docs) { res.send({ items: docs }); }); } } ..................... it('Get items', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }] var Item = { find: function(opts, cb) { setImmediate(function() { cb(null, items); }); } }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect({ items: items }, done); }); .....................
  33. Implement function index(options) { var Item = options.Item return function(req,

    res) { Item.find({}, function(err, docs) { res.send({ items: docs }); }); } } ..................... it('Get items', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }] var Item = { find: function(opts, cb) { setImmediate(function() { cb(null, items); }); } }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect({ items: items }, done); }); .....................
  34. Green

  35. Write a Test it('Pagenation with limit=30', function(done) { var items

    = _.range(0, 31).map(function(i) { return { id: i.toString(), name: 'name', price: 1200 } }); var Item = { find: function(opts, cb) { setImmediate(function() { cb(null, items); }); }, }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect(function(response) { assert.equal(response.body.items.length, 30) }) .end(done); });
  36. RED

  37. Implement function index(options) { var Item = options.Item return function(req,

    res) { Item.paginate({}, {}, function(err, docs) { res.send({ items: docs }); }); } } it('Pagenation with limit=30', function(done) { var items = _.range(0, 31).map(function(i) { return { id: i.toString(), name: 'name', price: 1200 } }); var Item = { paginate: function(query, opts, cb) { setImmediate(function() { var page = opts.page || 1 cb(null, items.slice(30 * (page - 1), 30 * page)); }) } }; .............. request(app) .get('/items') .expect(function(response) { assert.equal(response.body.items.length, 30) }) .end(done);
  38. Green

  39. TDD Cycle UPEPMJTUͷ࡞੒ ςετ࡞੒ 3FE ؀ڥઃఆɹ ػೳ࣮૷ (SFFO ϦϑΝΫλ ͩΜͩΜίʔυ͕ԚΕͯ͘Δ

    ଱͖͑Εͳ͘ͳͬͨΒ
 ϦϑΝΫλ
  40. 4. Refactor • ʮ֎޲͖ͷڍಈΛม͑ͣʹίʔυͷ಺෦ߏ଄ Λม͑Δ͜ͱʯ • TDDͷจ຺Ͱ͸ɺʮ֎޲͖ͷڍಈΛม͑ͳ ͍͜ͱʯ͸୯ମςετ͕อূ͢Δ • ϦϑΝΫλϦϯάͷ࠷த͸ৗʹςετ͕

    GreenͰ͋Δ͜ͱΛ֬ೝ
  41. Refactor 1: 
 ॏෳͷഉআ it('response body contains items', function(done) {

    var items = [] var Item = { paginate: function(query, opts, cb) { setImmediate(function() { var page = opts.page || 1 cb(null, items.slice(30 * (page - 1), 30 * page)); }) } }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect(function(response) { assert(response.body.items); }) .end(done) }); it('Get items', function(done) { var items = [{ id: '12345', name: 'Javascript: The Good Parts', price: 1800 }] var Item = { paginate: function(query, opts, cb) { setImmediate(function() { var page = opts.page || 1 cb(null, items.slice(30 * (page - 1), 30 * page)); }) } }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect({ items: items }, done); }); it('Pagenation with limit=30', function(done) { var items = _.range(0, 31).map(function(i) { return { id: i.toString(), name: 'name', price: 1200 } }); var Item = { paginate: function(query, opts, cb) { setImmediate(function() { var page = opts.page || 1 cb(null, items.slice(30 * (page - 1), 30 * page)); }) } }; var app = express(); app.get('/items', index({ Item: Item })); request(app) .get('/items') .expect(function(response) { assert.equal(response.body.items.length, 30) }) .end(done); }); }); function initMockDb(items) { return { paginate: function(query, opts, cb) { setImmediate(function() { var page = opts.page || 1 cb(null, items.slice(30 * (page - 1), 
 30 * page)); }) } }; } helper / util΁ͷॻ͖ग़͠
  42. Refactor 2: cb => Promise function index(options) { var Item

    = options.Item return function(req, res) { Item.paginate({}, {}, function(err, docs) { res.send({ items: docs }); }); } } function initMockDb(items) { return { paginate: function(query, opts, cb) { setImmediate(function() { var page = opts.page || 1 cb(null, items.slice(30 * (page - 1), 30 * page)); }) } }; } function index(options) { var Item = options.Item return function(req, res) { Item.paginate({}, {}) .then(function(docs) { res.send({ items: docs }); }); } } function initMockDb(items) { return { paginate: function(query, opts) { var page = opts.page || 1 var resp = items.slice(30 * (page - 1), return Promise.resolve(resp) } }; } callback styleͷAPIΛPromise΁ ֎޲͖ͷڍಈ͕͔Θ͍ͬͯͳ͍͜ͱΛ֬ೝ
  43. Refactor 3: ES2015 / 2016 function index(options) { var Item

    = options.Item return function(req, res) { Item.paginate({}, {}) .then(function(docs) { res.send({ items: docs }); }); } } function initMockDb(items) { return { paginate: function(query, opts) { var page = opts.page || 1 var resp = items.slice(30 * (page - 1), return Promise.resolve(resp) } }; } function index(options = {}) { const Item = options.Item return (req, res) => { Item.paginate({}, {}).then((docs) => { res.send({ items: docs }); }); } } function initMockDb(items) { return { paginate: (query, opts) => { const page = opts.page || 1 return Promise.resolve(
 items.slice(30 * (page - 1), 30 * page)) } }; }
  44. Refactor: ߟ࡯ • ”֎޲͖ͷڍಈΛม͑ͣʹίʔυͷ಺෦ߏ଄Λม ͑Δ͜ͱ” • ৽จ๏Λ࢖͍ͬͯͯ΋ɺڍಈ͕͔Θ͍ͬͯͳ ͍͜ͱ͸TDDͷϓϩηεͰ࡞੒ͨ͠୯ମςε τ͕อূͯ͘͠ΕΔ •

    ҆৺ͯ͠ίʔυΛ৽͍͠จ๏ʹ௚͍͚ͯ͠Δ
  45. Conclusion • ςετ㱻࣮૷Λ܁Γฦ͠ɺզຫͰ͖ͳ͘ͳͬ ͨΒϦϑΝΫλ • TDD͸ܧଓతϦϑΝΫλͷ؀ڥΛ࡞Δͱ͍͏ ఺Ͱ΋ඇৗʹ༗ޮ • ݴޠͷਐԽ͕ૣ͍Javascriptͦ͜TDDΛ
 ಋೖ͍ͯ͜͠͏ʂ

  46. Thank you for listening!