Edited at

ワイのテスト駆動開発〜偶数ハロー株式会社〜


業務中ワイ

ワイ「こないだ見た50分でわかるテスト駆動開発っていう動画おもろかったなぁ」

ワイ「ワイもテスト駆動開発やってみたいわ〜」

ワイ「まずは何か簡単な案件でTDDを導入してみたいわ〜」


そんなとき、クライアントから電話

ジリリリリ〜〜〜〜〜ン!1

20C26BBD-EF6B-4AA7-B9B5-BA5454131FB8.jpeg

ワイ「もしもし〜?」

??「もしもし、株式会社ブラック暗井 暗人(くらい・あんと)ですー」

ワイ「ああ、暗井はんでっか」

ワイ「いつもお世話になってます〜」

暗井「この前ご相談した件なんですけど、正式に発注お願いしますわー」

暗井「株式会社偶数ハローさんのWEBサイトの件ですわー」

ワイ「どんな案件でしたっけ?」

暗井「偶数ハローいう社名にちなんで、」

暗井「トップページにちょっとした仕掛けを設置しよう、いうやつですわ」

暗井「要は───」



  • ユーザーが奇数を入力したら、その数値をそのまま表示する

  • ユーザーが偶数を入力したら、「ハロー」と表示する


暗井「───っていうフォームをトップページに設置するんですわ」

社長「(何やそのクソみたいなフォーム・・・)」

暗井「フォームの見た目とかはもう出来てるんですけど」

暗井「数値かハローを返す関数、これが作れませんで」

暗井「そこの関数だけ作って欲しいんですわ」

暗井「予算は800万ですわ」

ワイ「かしこまりやで」

ワイ「早速そのプログラム作ってみますわ」


ちょうどいい案件が舞い込んできた

ワイ「いや〜」

ワイ「ちょうどTDDの練習におあつらえ向きな」

ワイ「シンプルな案件が舞い込んでくるもんやな〜」

社長「(筆者がそのようにストーリーを作ってるからな・・・)」

社長「(なんや株式会社偶数ハローて。何を願って命名したんや)」

ワイ「ほなTDDしていこか〜」


テストツールを導入する

ワイ「ほなJestいう、有名なテストツールでも導入してみよか」

ワイ「yarn add --dev jestと」

ワイ「おお、簡単やな」

ワイ「そんで、evenHello.jsいうファイルでも作っとこか」


evenHello.js

function evenHello() {

}
module.exports = evenHello;


ワイ「こうやな」

ワイ「evenHelloいう関数を作って、エクスポートするわけや」

ワイ「次はテストを書くファイルを作るで」


test.js

const evenHello = require('./evenHello');

// ここにテストを書いていく


ワイ「こうやな」

ワイ「さっきのevenHello.jsを読み込んで」

ワイ「それに関するテストをここに書いていくんや」

ワイ「ほな、まず関数を書いていこか」

ワイ「とりあえず───」



  • 奇数を渡したら、その数値をそのまま返す


ワイ「───って部分を実装してみよか」


evenHello.js

function evenHello(num) {

return num;
}
module.exports = evenHello;

ワイ「こうやな」

ワイ「あれ・・・?」


これではいつも通りやん

ワイ「アカンアカン」

ワイ「無意識に実装から始めてもうたわ」

ワイ「ちゃうねん」

ワイ「テスト駆動開発なんやから、テストが先や」

ワイ「実装するより前に、要件を満たしたテストをまず書く

ワイ「そして、そのテストはもちろん通らへん」

ワイ「実装してないからな」

ワイ「そんで、そのあと、テストが通るように実装をしていくねん」

ワイ「先に要件をテスト化してから実装するから」

ワイ「正解に向かってしか進めへんって感じやな」

ワイ「そのためには、ちゃんと要件定義をせなあかんけどな」

ワイ「せやから・・・」

ワイ「一旦evenHello.jsは削除や!


仕切り直してテスト駆動開発

ワイ「まずは要件定義をしてみよか」

ワイ「今回作るべき関数の要件は」


要件チェックリスト:



  • 奇数を渡したら、その数値をそのまま返す


  • 偶数を渡したら、「ハロー」を返す


ワイ「こうやな」

ワイ「さらに細かく、具体的に定義していくで」


要件チェックリスト:



  • 奇数を渡したら、その数値をそのまま返す



    • 1を渡したら、1を返す


    • 3を渡したら、3を返す




  • 偶数を渡したら、「ハロー」を返す



    • 2を渡したら、「ハロー」を返す


    • 4を渡したら、「ハロー」を返す




ワイ「こんな感じやな」

ワイ「ほな、まずは」




  • 1を渡したら、1を返す


ワイ「これのテストを書くで」


test.js

const evenHello = require('./evenHello');

test('1を渡したら、1を返す', () => {
expect(evenHello(1)).toBe(1);
});


ワイ「こうやな」

ワイ「ほんでテスト実行や」

ワイ「yarn testと」


Test suite failed to run

Cannot find module './evenHello' from 'test.js'


ワイ「せやな」

ワイ「evenHelloCannot findやからfailedや!」

ワイ「evenHello.jsはさっき削除したからな」

ワイ「ほな、テストを通すために、もう一回evenHello.jsを作るで!」


evenHello.js

function evenHello(num) {

}
module.exports = evenHello;


ワイ「こうやな」

ワイ「テストに合わせて、evenHello.jsっていうファイルを作って」

ワイ「テストに合わせて、evenHelloいう関数を書く、と」

ワイ「全てはテスト駆動や!」

ワイ「ほんで、テスト実行、と」


テスト結果:

✕ 1を渡したらそのまま1を返す


ワイ「お、一応テストは走ったけど、バツ印やな」

ワイ「ちゃんと実装してへんもんな」

ワイ「ほな、まずこのテストだけ通るように仮実装してみよか」


evenHello.js

function evenHello(num) {

return num;
}
module.exports = evenHello;

ワイ「こうやな」

ワイ「引数numそのまま返すわけや」

ワイ「ほんで、テスト実行、と」


テスト結果:

✓ 1を渡したら、1を返す


ワイ「お、今度は緑のチェックがついたな」

ワイ「ここのテストは通ったいうわけや」


要件チェックリスト:



  • 奇数を渡したら、その数値をそのまま返す



    • 1を渡したら、1を返す


    • 3を渡したら、3を返す




  • 偶数を渡したら、「ハロー」を返す



    • 2を渡したら、「ハロー」を返す


    • 4を渡したら、「ハロー」を返す




ワイ「進捗としては↑こんな感じやな」

ワイ「ほな次は───」




  • 3を渡したら、3を返す


ワイ「───これのテストを書いてみよか」


test.js

const evenHello = require('./evenHello');

test('1を渡したら、1を返す', () => {
expect(evenHello(1)).toBe(1);
});

test('3を渡したら、3を返す', () => {
expect(evenHello(3)).toBe(3);
});


ワイ「これは、evenHello関数は特にいじらなくてもテスト通りそうやな」

ワイ「ほなテスト実行、と」


テスト結果:

✓ 1を渡したら、1を返す

✓ 3を渡したら、3を返す


ワイ「よっしゃ、通ったな」

ワイ「ほな次は・・・テストをまた書くんやな」

ワイ「テストが先やからな」


test.js

test('2を渡したら、「ハロー」を返す', () => {

expect(evenHello(2)).toBe('ハロー');
});

ワイ「↑このテストを追加や」

ワイ「ほんで、テスト実行、と」


テスト結果:

✓ 1を渡したら、1を返す

✓ 3を渡したら、3を返す

✕ 2を渡したら、「ハロー」を返す


ワイ「せやな」

ワイ「偶数ハロー機能はまだ実装してへんからな」

ワイ「よっしゃ、今度は実装や」


evenHello.js

function evenHello(num) {

if (num % 2) {
return num;
}
return 'ハロー';
}
module.exports = evenHello;

ワイ「引数num2で割って」

ワイ「余りがあったなら、num奇数やって事やから」

ワイ「そのままnumを返すんや」

ワイ「逆に、余りがないなら偶数やから」

ワイ「返すのはハローや」

ワイ「これでええやろ」

ワイ「テスト実行、と」


テスト結果:

✓ 1を渡したら、1を返す

✓ 3を渡したら、3を返す

✓ 2を渡したら、「ハロー」を返す


ワイ「うんうん、せやろな」

ワイ「なんかこう、これまでの全要件をテストしてくれるから」

ワイ「継ぎ足し継ぎ足しでコードを書いていっても」


ワイ「さっきまではちゃんと出来てた部分が、知らんうちにダメになってたやん!


ワイ「ってことがないから安心やな」

ワイ「ほな最後は」




  • 4を渡したら、「ハロー」を返す


ワイ「↑この要件のテストでも書いとこか」


test.js

test('4を渡したら、「ハロー」を返す', () => {

expect(evenHello(4)).toBe('ハロー');
});

ワイ「こうやな」

ワイ「ほんで、テスト実行、と」


テスト結果:

✓ 1を渡したら、1を返す

✓ 3を渡したら、3を返す

✓ 2を渡したら、「ハロー」を返す

✓ 4を渡したら、「ハロー」を返す


ワイ「よっしゃ、偶数ハロー関数の完成やな」

ワイ「チェックリストでいうと───」


要件チェックリスト:



  • 奇数を渡したら、その数値をそのまま返す



    • 1を渡したら、1を返す


    • 3を渡したら、3を返す




  • 偶数を渡したら、「ハロー」を返す



    • 2を渡したら、「ハロー」を返す


    • 4を渡したら、「ハロー」を返す




ワイ「───こんな感じや」

ワイ「オールOKや」


せっかくだから、チェックリストに近いテストを

ワイ「確かJestの機能で」

ワイ「テストをグループ化できんねん」


test.js

describe('奇数を渡したら、その数値をそのまま返す', () => {

test('1を渡したら、1を返す', () => {
expect(evenHello(1)).toBe(1);
});
test('3を渡したら、3を返す', () => {
expect(evenHello(3)).toBe(3);
});
});

describe('偶数を渡したら、「ハロー」を返す', () => {
test('2を渡したら、「ハロー」を返す', () => {
expect(evenHello(2)).toBe('ハロー');
});
test('4を渡したら、「ハロー」を返す', () => {
expect(evenHello(4)).toBe('ハロー');
});
});


ワイ「こないな感じや」

ワイ「チェックリスト同様に入れ子にしてやるんや

ワイ「そしたら、今後この案件に関わる子らにとっても分かりやすいやろ」

ワイ「ほんで、テスト実行してみよか」


テスト結果:

奇数を渡したら、その数値をそのまま返す

 ✓ 1を渡したら、1を返す

 ✓ 3を渡したら、3を返す

偶数を渡したら、「ハロー」を返す

 ✓ 2を渡したら、「ハロー」を返す

 ✓ 4を渡したら、「ハロー」を返す


ワイ「うんうん」

ワイ「ちゃんとテスト結果もグループ分けされてて直感的やん」

ワイ「はぁ〜」

ワイ「デグレ2してないことがテストによって保証されてるから」

ワイ「安心して進んでいける感じがしたな」

ワイ「ほな、クライアントはんに連絡してみよか」


暗井暗人はんに報告

ワイ「もしもし〜、暗井はん?さっきの関数作りましたで〜」

ワイ「いつものリポジトリに上げといたんで、確認してくださいやで〜」

暗井「はいよー」

暗井「うんうん、動かしてみたけどええ感じやね」

ワイ「よかったですわ〜」

暗井「・・・あっ!!!

ワイ「な、なんでっか・・・」

暗井「株式会社偶数ハローさん、先週社名変更したんでしたわ」

暗井「その名も、株式会社偶数ハロー・マイナスワールド

暗井「それに伴って関数の仕様にも───」



  • 負の値を渡したら、「ワールド」を返す


暗井「───これが追加になるんでしたわ」

暗井「さっき言い忘れてましたわ」

ワイ「はぁ・・・・」

暗井「すんまへんけど、その修正お願いしますわ・・・」

暗井「ちなみにこの修正も、さっきの800万の中で収まりまっか・・・?」

ワイ「何とか800万で収めますわ・・・」


修正開始

ワイ「はぁ、一番やる気なくなるやつや・・・」

ワイ「でもまぁ、テスト駆動開発の練習にはちょうどええかもな」

ワイ「さっそく、要件定義&テストから始めるで!」

ワイ「追加の要件は・・・」




  • 負の値を渡したら、偶数奇数に関わらず「ワールド」を返す



    • -1を渡したら、「ワールド」を返す


    • -2を渡したら、「ワールド」を返す




ワイ「こんな感じやな」

ワイ「せやから・・・」


test.js

describe('負の値を渡したら、偶数奇数に関わらず「ワールド」を返す', () => {

test('-1を渡したら、「ワールド」を返す', () => {
expect(evenHello(-1)).toBe('ワールド');
});
test('-2を渡したら、「ワールド」を返す', () => {
expect(evenHello(-2)).toBe('ワールド');
});
});

ワイ「↑これをテストに追加や!」

ワイ「そんでテスト実行!」


テスト結果:

負の値を渡したら、偶数奇数に関わらず「ワールド」を返す

 ✕ -1を渡したら、「ワールド」を返す

 ✕ -2を渡したら、「ワールド」を返す

奇数を渡したら、その数値をそのまま返す

 ✓ 1を渡したら、1を返す

 ✓ 3を渡したら、3を返す

偶数を渡したら、「ハロー」を返す

 ✓ 2を渡したら、「ハロー」を返す

 ✓ 4を渡したら、「ハロー」を返す


ワイ「せやな」

ワイ「追加したテストだけバツ。想定通りや」

ワイ「ほんで今度は実装や!」


evenHello.js

function evenHello(num) {

if (num < 0) {
return 'ワールド';
}
if (num % 2) {
return num;
}
return 'ハロー';
}
module.exports = evenHello;

ワイ「こうやな!」

ワイ「ほんでテスト実行や!」


テスト結果:

負の値を渡したら、偶数奇数に関わらず「ワールド」を返す

 ✓ -1を渡したら、「ワールド」を返す

 ✓ -2を渡したら、「ワールド」を返す

奇数を渡したら、その数値をそのまま返す

 ✓ 1を渡したら、1を返す

 ✓ 3を渡したら、3を返す

偶数を渡したら、「ハロー」を返す

 ✓ 2を渡したら、「ハロー」を返す

 ✓ 4を渡したら、「ハロー」を返す


ワイ「うんうん」

ワイ「追加の要件も満たしたし」

ワイ「元々の機能もおかしくなってへん

ワイ「それが可視化されてるからものすごい安心感や!

ワイ「こんなに安心して追加開発が出来るのは初めてやで・・・」

ワイ「いつも、コード修正するときに」

ワイ「言われた部分は修正できたけど、他んとこおかしくなってへんかな・・・って」

ワイ「心配で夜も6時間しか眠れへんかったんや」

社長「(まあまあ寝てるやん・・・)」

社長「(あと、そんな心配なもん納品すな・・・)」


まとめ

ワイ「なんか、追加開発時の安心感がすごいわ」

ワイ「デグレしてへんことをテストが保証してくれてんねんもん」

ワイ「あと、テストコード自体がドキュメント代わりになるな」

ワイ「途中から参加したメンバーも、test.jsを見れば」

ワイ「おー、こんな要件を満たす必要があるんやなー、って分かるやん?」

ワイ「そういう情報がコードとして残るのが素晴らしいな」

ワイ「よっしゃ、今度はもうちょい複雑な案件でもテスト駆動開発してみよか!」

〜おしまい〜


おすすめ動画

ワイの記事はだいぶ端折ってる感じやから、興味がある人は↓この動画を見るとええで!

50分でわかるテスト駆動開発





  1. 電話の音やで。 



  2. 前は機能していた部分がバグってしまうことや。