Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Starting TDD with Node.js

Akito0107
November 12, 2016

Starting TDD with Node.js

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

Akito0107

November 12, 2016
Tweet

More Decks by Akito0107

Other Decks in Programming

Transcript

  1. Node.jsͰ࢝ΊΔ

    TDD
    Akito Ito

    View Slide

  2. ࣗݾ঺հ
    • ҏ౻ɹӯɹ(͍ͱ͏ ͖͋ͱ)
    • ϦΫϧʔτςΫϊϩδʔζͱ͍͏ձࣾͰ

    Node.js੡γεςϜͷӡ༻։ൃΛ͍ͯ͠·͢
    • @Akito0107

    View Slide

  3. ES2015 / ES2016 / ES2017
    • Node.js (ͱv8) ͷαϙʔτ͕ͲΜͲΜਐΜͰ
    ͍Δ
    • Node6͸ES2015 99%αϙʔτ
    • Node4 (1೥લ)͸57%….
    • ͜ͷྲྀΕʹ৐Γ஗Εͨ͘ͳ͍

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  7. View Slide

  8. ݱ࣮͸ݫ͍͠
    • ՔಇதͷαʔϏεΛES2015 / 2016ʹఴͬͯ

    ॻ͖௚͢ͷ͸؆୯Ͱ͸ͳ͍ɻ
    • ݴ͍Α͏ͷͳ͍ෆ҆ͱͷઓ͍
    • ͱ͸͍͑ɺ৽͍͠ػೳ΋࢖͍͖͍ͬͯͨ

    View Slide

  9. ݱ࣮͸ݫ͍͠
    • ՔಇதͷαʔϏεΛES2015 / 2016ʹఴͬͯ

    ॻ͖௚͢ͷ͸؆୯Ͱ͸ͳ͍ɻ
    • ݴ͍Α͏ͷͳ͍ෆ҆ͱͷઓ͍
    • ͱ͸͍͑ɺ৽͍͠ػೳ΋࢖͍͖͍ͬͯͨ
    TDD

    View Slide

  10. What is TDD ??
    • Test Driven Development 

    (ςετۦಈ։ൃ)
    • ։ൃର৅ͷػೳʹରͯ͠ҎԼͷϓϩηεΛ܁Γฦ͢;

    1. ςετΛॻ͖ɺfailͤ͞Δ

    2. ػೳΛ࣮૷͠ɺςετΛpassͤ͞Δ

    3. ϦϑΝΫλ

    View Slide

  11. TDD Cycle
    UPEPMJTUͷ࡞੒
    ςετ࡞੒ 3FE

    ؀ڥઃఆɹ
    ػೳ࣮૷ (SFFO

    ϦϑΝΫλ

    View Slide

  12. 0. ؀ڥઃఆ
    • Testing framework / test runner
    • mocha / ava / eater / etc…
    • ςετࣗಈԽπʔϧ΋ඞਢ
    • ϑΝΠϧมߋΛݕ஌ͯࣗ͠ಈͰtestΛճ͢
    • npm-watch / mocha -w / etc…

    View Slide

  13. 1. TodoϦετͷ࡞੒
    • ࠓ͔Β࣮૷͢ΔػೳϦετ
    • ࢓༷ͳͲ͕൑໌͍ͯ͠Δ৔߹͸ͦΕ΋

    ͔ͬ͠Γॻ͘
    • ࣮૷ͷॱ൪Λ༏ઌॱҐ΍TestabilityʹΑͬͯ

    ܾఆ͢Δ
    • ஍ຯʹେࣄͳͷͰαϘΒͣʹॻ͜͏

    View Slide

  14. Example: todo list
    • (expressͷshopping applicationΛ૝ఆ)
    • API GET /items
    • response bodyʹ ‘items’ property͕͋Δ
    • items͸DB͔ΒऔಘͰ͖Δʢࠓճ͸mock)
    • pagination (limit: 30)

    View Slide

  15. 2. Write a test
    • Todo list͔Β1ͭػೳΛpopͯ͠ɺ࠷ॳʹςε
    τΛॻ͘
    • ඞͣςετ͕མͪΔ͜ͱΛ֬ೝ͢Δʂ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  20. Fail (RED)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  24. Pass (Green)

    View Slide

  25. TDD Cycle
    UPEPMJTUͷ࡞੒
    ςετ࡞੒ 3FE

    ؀ڥઃఆɹ
    ػೳ࣮૷ (SFFO

    ϦϑΝΫλ
    2ͱ3Λ܁Γฦ͢

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. Red

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  34. Green

    View Slide

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

    View Slide

  36. RED

    View Slide

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

    View Slide

  38. Green

    View Slide

  39. TDD Cycle
    UPEPMJTUͷ࡞੒
    ςετ࡞੒ 3FE

    ؀ڥઃఆɹ
    ػೳ࣮૷ (SFFO

    ϦϑΝΫλ
    ͩΜͩΜίʔυ͕ԚΕͯ͘Δ
    ଱͖͑Εͳ͘ͳͬͨΒ

    ϦϑΝΫλ

    View Slide

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

    View Slide

  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΁ͷॻ͖ग़͠

    View Slide

  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΁
    ֎޲͖ͷڍಈ͕͔Θ͍ͬͯͳ͍͜ͱΛ֬ೝ

    View Slide

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

    View Slide

  44. Refactor: ߟ࡯
    • ”֎޲͖ͷڍಈΛม͑ͣʹίʔυͷ಺෦ߏ଄Λม
    ͑Δ͜ͱ”
    • ৽จ๏Λ࢖͍ͬͯͯ΋ɺڍಈ͕͔Θ͍ͬͯͳ
    ͍͜ͱ͸TDDͷϓϩηεͰ࡞੒ͨ͠୯ମςε
    τ͕อূͯ͘͠ΕΔ
    • ҆৺ͯ͠ίʔυΛ৽͍͠จ๏ʹ௚͍͚ͯ͠Δ

    View Slide

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

    ಋೖ͍ͯ͜͠͏ʂ

    View Slide

  46. Thank you for listening!

    View Slide