Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Starting TDD with Node.js
Akito0107
November 12, 2016
Programming
3
5.3k
Starting TDD with Node.js
Node学園祭2016でのLT資料です。
Akito0107
November 12, 2016
Tweet
Share
More Decks by Akito0107
See All by Akito0107
テスタビリティの高いGoのAPIサーバを開発しよう
akito0107
2
2.9k
TypeScriptのコマンドラインパーサー
akito0107
1
190
Node学園 25限 Introduction to Rust
akito0107
6
1.1k
Other Decks in Programming
See All in Programming
SRE NEXT 2022: Sensible Incident Management for Software Startups
takanabe
2
860
Composing an API with Kotlin (Kotlin Dev Day 2022)
zsmb
0
310
Viteはいいぞ/Vite is Good
dojineko
1
110
Introducing Kotlin Multiplatform in an existing project | Kotlin Dev Day Amsterdam
prof18
0
310
CLI構築のススメ
nyankotaro
1
260
How useEvent would change our applications
koba04
1
1.8k
偏見と妄想で語るスクリプト言語としての Swift / Swift as a Scripting Language
lovee
2
300
UI State Modeling 어떤게 좋을까?
laco2951
1
250
Micro Frontends with Module Federation: Beyond the Basics @codecrafts2022
manfredsteyer
PRO
0
130
You may not need JavaScript
simas
0
390
SRE bridge the gap: Feature development to Core API / 機能開発チームとコアAPIチームの架け橋としてのSRE
kenzan100
1
610
デュアルトラックアジャイル× Agile Testingから 見えてきたQAのミライ
atamaplus
0
500
Featured
See All Featured
Bootstrapping a Software Product
garrettdimon
296
110k
Pencils Down: Stop Designing & Start Developing
hursman
112
9.8k
Docker and Python
trallard
27
1.5k
Reflections from 52 weeks, 52 projects
jeffersonlam
337
17k
What's in a price? How to price your products and services
michaelherold
229
9.3k
jQuery: Nuts, Bolts and Bling
dougneiner
56
6.4k
Six Lessons from altMBA
skipperchong
14
1.3k
Building Flexible Design Systems
yeseniaperezcruz
310
33k
What's new in Ruby 2.0
geeforr
336
30k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
498
130k
Principles of Awesome APIs and How to Build Them.
keavy
113
15k
Why You Should Never Use an ORM
jnunemaker
PRO
47
5.6k
Transcript
Node.jsͰ࢝ΊΔ TDD Akito Ito
ࣗݾհ • ҏ౻ɹӯɹ(͍ͱ͏ ͖͋ͱ) • ϦΫϧʔτςΫϊϩδʔζͱ͍͏ձࣾͰ Node.jsγεςϜͷӡ༻։ൃΛ͍ͯ͠·͢ • @Akito0107
ES2015 / ES2016 / ES2017 • Node.js (ͱv8) ͷαϙʔτ͕ͲΜͲΜਐΜͰ ͍Δ
• Node6ES2015 99%αϙʔτ • Node4 (1લ)57%…. • ͜ͷྲྀΕʹΓΕͨ͘ͳ͍
ΧδϡΞϧʹม • e.g. ES2015෩ʹॻ͖͢ function plzRefactorToArrow() { return { value:
0, increment: function() { ++this.value; return this.value; } } }
ΧδϡΞϧʹม • e.g. ES2015෩ʹॻ͖͢ function arrow() { return { value:
0, increment: () => { ++this.value; return this.value; } } }
ΧδϡΞϧʹม • e.g. ES2015෩ʹॻ͖͢ function arrow() { return { value:
0, increment: () => { ++this.value; return this.value; } } }
None
ݱ࣮ݫ͍͠ • ՔಇதͷαʔϏεΛES2015 / 2016ʹఴͬͯ ॻ͖͢ͷ؆୯Ͱͳ͍ɻ • ݴ͍Α͏ͷͳ͍ෆ҆ͱͷઓ͍ • ͱ͍͑ɺ৽͍͠ػೳ͍͖͍ͬͯͨ
ݱ࣮ݫ͍͠ • ՔಇதͷαʔϏεΛES2015 / 2016ʹఴͬͯ ॻ͖͢ͷ؆୯Ͱͳ͍ɻ • ݴ͍Α͏ͷͳ͍ෆ҆ͱͷઓ͍ • ͱ͍͑ɺ৽͍͠ػೳ͍͖͍ͬͯͨ
TDD
What is TDD ?? • Test Driven Development (ςετۦಈ։ൃ)
• ։ൃରͷػೳʹରͯ͠ҎԼͷϓϩηεΛ܁Γฦ͢; 1. ςετΛॻ͖ɺfailͤ͞Δ 2. ػೳΛ࣮͠ɺςετΛpassͤ͞Δ 3. ϦϑΝΫλ
TDD Cycle UPEPMJTUͷ࡞ ςετ࡞ 3FE ڥઃఆɹ ػೳ࣮ (SFFO ϦϑΝΫλ
0. ڥઃఆ • Testing framework / test runner • mocha
/ ava / eater / etc… • ςετࣗಈԽπʔϧඞਢ • ϑΝΠϧมߋΛݕͯࣗ͠ಈͰtestΛճ͢ • npm-watch / mocha -w / etc…
1. TodoϦετͷ࡞ • ࠓ͔Β࣮͢ΔػೳϦετ • ༷ͳͲ͕໌͍ͯ͠Δ߹ͦΕ ͔ͬ͠Γॻ͘ • ࣮ͷॱ൪Λ༏ઌॱҐTestabilityʹΑͬͯ ܾఆ͢Δ
• ຯʹେࣄͳͷͰαϘΒͣʹॻ͜͏
Example: todo list • (expressͷshopping applicationΛఆ) • API GET /items
• response bodyʹ ‘items’ property͕͋Δ • itemsDB͔ΒऔಘͰ͖Δʢࠓճmock) • pagination (limit: 30)
2. Write a test • Todo list͔Β1ͭػೳΛpopͯ͠ɺ࠷ॳʹςε τΛॻ͘ • ඞͣςετ͕མͪΔ͜ͱΛ֬ೝ͢Δʂ
࠷ॳͷ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) }); });
࠷ॳͷ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) }); });
࠷ॳͷ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) }); });
࠷ॳͷ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) }); });
Fail (RED)
3. Production Codeͷ࣮ • Test͕Pass͢Δ࠷খݶͷίʔυΛॻ͘ • (͜ͷஈ֊Ͱ)ՄಡੑͳͲߟྀ͠ͳ͍
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) }); });
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) }); });
Pass (Green)
TDD Cycle UPEPMJTUͷ࡞ ςετ࡞ 3FE ڥઃఆɹ ػೳ࣮ (SFFO ϦϑΝΫλ 2ͱ3Λ܁Γฦ͢
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); }); });
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); }); });
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); }); });
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); }); });
Red
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); }); .....................
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); }); .....................
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); }); .....................
Green
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); });
RED
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);
Green
TDD Cycle UPEPMJTUͷ࡞ ςετ࡞ 3FE ڥઃఆɹ ػೳ࣮ (SFFO ϦϑΝΫλ ͩΜͩΜίʔυ͕ԚΕͯ͘Δ
͖͑Εͳ͘ͳͬͨΒ ϦϑΝΫλ
4. Refactor • ʮ֎͖ͷڍಈΛม͑ͣʹίʔυͷ෦ߏ Λม͑Δ͜ͱʯ • TDDͷจ຺Ͱɺʮ֎͖ͷڍಈΛม͑ͳ ͍͜ͱʯ୯ମςετ͕อূ͢Δ • ϦϑΝΫλϦϯάͷ࠷தৗʹςετ͕
GreenͰ͋Δ͜ͱΛ֬ೝ
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ͷॻ͖ग़͠
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 ֎͖ͷڍಈ͕͔Θ͍ͬͯͳ͍͜ͱΛ֬ೝ
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)) } }; }
Refactor: ߟ • ”֎͖ͷڍಈΛม͑ͣʹίʔυͷ෦ߏΛม ͑Δ͜ͱ” • ৽จ๏Λ͍ͬͯͯɺڍಈ͕͔Θ͍ͬͯͳ ͍͜ͱTDDͷϓϩηεͰ࡞ͨ͠୯ମςε τ͕อূͯ͘͠ΕΔ •
҆৺ͯ͠ίʔυΛ৽͍͠จ๏ʹ͍͚ͯ͠Δ
Conclusion • ςετ㱻࣮Λ܁Γฦ͠ɺզຫͰ͖ͳ͘ͳͬ ͨΒϦϑΝΫλ • TDDܧଓతϦϑΝΫλͷڥΛ࡞Δͱ͍͏ Ͱඇৗʹ༗ޮ • ݴޠͷਐԽ͕ૣ͍Javascriptͦ͜TDDΛ ಋೖ͍ͯ͜͠͏ʂ
Thank you for listening!