ブラウザだけではじめるミニマムテスト駆動開発
こんにちわ、ソニックムーブのセイカイです。
この投稿はソニックムーブ Advent Calendar 201318日目の投稿になります。
さて、今回はテスト駆動開発について書いてみることにします。
テスト駆動開発と聞くと、割とプログラマっぽい響きに敬遠してしまうひともいるかもしれませんが、全然そんなことはないですよ。
ということで簡単に手の届きやすいところからということでJavaScriptを用いてライブラリ等を使わずブラウザだけではじめて見ましょう。
前置き
ちなみにテスト駆動開発はWikiによると
テスト駆動開発 (てすとくどうかいはつ、test-driven development; TDD) とは、プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと言う)、そのテストが動作する必要最低限な実装をとりあえず行った後、コードを洗練させる、という短い工程を繰り返すスタイルである。
簡単に言うと、先に要件ベースでテストコード書いて、それが通る実装をする、実装したらテストが通る部分を担保したままリファクタリングすれば良いですね的なノリですね。
メリットでメリット
「要件に対して正しく動作する」という判定を自動化することで、コードの変更が起こった時や修正時に、この部分を担保しやすいというのがテストコードを書く主なメリットです。
すなわち、コードの品質を担保しやすいって話ですね。
デメリットとしてはテストコード自体をメンテナンスしないと、更新されない仕様書と同じく意味の無いものになりますので、その分のコストは見込む必要があることでしょうか。
また、テストコードは適切な粒度で書かないと、細か過ぎると手間が増え、大雑把過ぎるとあまり意味が無いので注意です。
テストコード
上記におけるテストコードは主に、assertというクラスなり関数なりを利用するものが主流です。今回は、ブラウザに実装されているconsole.assertを使ってみます。
console.assert
console.assertは第一引数が評価式で、第二引数が評価式がfalseを返した時に出力されるメッセージになります。
1 2 |
[sourcecode lang="javascript"]console.assert(true, "失敗時のメッセージ"); [/sourcecode] |
上記は評価式がtrueになるので、何も起こりません。
1 2 |
[sourcecode lang="javascript"]console.assert(false, "失敗時のメッセージ"); [/sourcecode] |
上記は、エラーと共に「失敗時のメッセージ」が出力されます。
準備
今回はとりあえずFizzBuzzのプログラムをテスト駆動で書いてみることにします。
ちなみに、FizzBuzzは「1 から順に数を数えて、その数が 3 で割り切れるならばFizz と、5 で割り切れるなら Buzz と言うゲーム。3 でも 5 でも割り切れる場合は、FizzBuzz の順に言う」
今回は1から100までをカウントしてみます。
ファイル構成は下記の用にして空のjsファイルを2つと、htmlファイルを用意します。
index.html
┣fizzBuzz.js
┗testFizzBuzz.js
index.htmlは下記の用に2つのjsファイルを読み込みます。
1 2 3 4 5 6 7 8 9 10 11 12 |
[sourcecode lang="html"]<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"> <title>FizzBuzz</title> </head> <body> <script type="text/javascript" src="fizzBuzz.js"></script> <script type="text/javascript" src="testFizzBuzz.js"></script> </body> </html> [/sourcecode] |
これで準備は完了です。
プログラムの全体
数字をカウントアップする過程で下記の処理を行うのがプログラムの要件となります。
- 3で割り切れる=>「Fizz」を出力
- 5で割り切れる=>「Buzz」を出力
- 3でも5で割り切れる=>「FizzBuzz」を出力
- 上記以外=>そのまま数字を出力
縮めれば1行で書けるコードですが、テストを前提にというところでカウント毎の数字を処理する関数をつくり、その結果を評価する方式とします。
STEP1:Fizz
fizzBuzz.jsに下記のようなコードを書きます。
1 2 3 4 |
[sourcecode lang="javascript"]function fizzBuzz(num) { return ""; } [/sourcecode] |
fizzBuzz関数はとりあえず空文字列を返す様にしておきます。
次にtestFizzBuzz.jsにテストコードを書きます。
今回は簡単な用件なので一気に書いてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[sourcecode lang="javascript"]//3で割り切れる=&gt;「Fizz」が返ってくる console.assert( fizzBuzz( 3 ) === 'Fizz', '1' ); console.assert( fizzBuzz( 33 ) === 'Fizz', '2' ); //5で割り切れる=>「Buzz」が返ってくる console.assert( fizzBuzz( 5 ) === 'Buzz', '3' ); console.assert( fizzBuzz( 50 ) === 'Buzz', '4' ); //3でも5で割り切れる=>「FizzBuzz」が返ってくる console.assert( fizzBuzz( 15 ) === 'FizzBuzz', '5' ); console.assert( fizzBuzz( 45 ) === 'FizzBuzz', '6' ); //上記以外=>そのまま数字が返ってくる console.assert( fizzBuzz( 4 ) === '4', '7' ); console.assert( fizzBuzz( 17 ) === '17', '8' ); [/sourcecode] |
※今回は説明用に失敗時のメッセージに番号を入れていますが、本来は処理結果に対して分かりやすいメッセージを入れる方が良いです。
index.htmlをブラウザで開くと、fizzBuzz関数は期待した値を返さないので全てのテストに失敗しエラーメッセージが出力されます。
とりあえず、3で割り切れる場合Fizzを返す様にします。
1 2 3 4 5 6 7 8 9 10 11 |
[sourcecode lang="javascript"]function fizzBuzz(num) { var result; if ( num%3 === 0 ) { result = 'Fizz'; } else { result = ""; } return result; } [/sourcecode] |
これで、とりあえずテストが2まで通りますね。
STEP2:Buzz
こんどは5で割り切れる場合Buzzを返す様にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
[sourcecode lang="javascript"]function fizzBuzz(num) { var result; if ( num%3 === 0 ) { result = 'Fizz'; } else if ( num%5 === 0 ) { result = 'Buzz'; } else { result = ""; } return result; } [/sourcecode] |
これで、とりあえずテストが4まで通りますね。
STEP3: FizzBuzz
次は、3でも5で割り切れる数字がFizzBuzzを返す様に実装を見直します。
基本的には、この繰り返しで順にテストが通る様にしていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[sourcecode lang="javascript"]function fizzBuzz(num) { var result; if ( num%3 === 0 && num%5 === 0 ) { result = 'FizzBuzz'; } else if ( num%3 === 0 ) { result = 'Fizz'; } else if ( num%5 === 0 ) { result = 'Buzz'; } else { result = ""; } return result; } [/sourcecode] |
まだ7と8が失敗するので実装を見直します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[sourcecode lang="javascript"]function fizzBuzz(num) { var result; if ( num%3 === 0 && num%5 === 0 ) { result = 'FizzBuzz'; } else if ( num%3 === 0 ) { result = 'Fizz'; } else if ( num%5 === 0 ) { result = 'Buzz'; } else { result = String(num); } return result; } [/sourcecode] |
これで全てのテストを通過しますね。
最後にカウントアップの処理を追加して完成です。
1 2 3 4 |
[sourcecode lang="javascript"]for(var i=1; i < 100; i++) { console.log( fizzBuzz( i ) ); } [/sourcecode] |
以降要件が追加されれば、テストコードを追加し、実装を見直すという手順で修正していきます。
今回は単純なものでしたので、「コードを洗練させる」という部分には触れていませんが、とりあえずこんな感じで、日々簡単なところからテストコードを書く習慣をつけていくことで、コードの品質が上げていけるのではないでしょうか。
応用
今回は簡単なケースですので、単純なテストコードとなりましたが、複雑なものであれば前後に処理が入るケースもあるかと思います。
例えばFizBuzzの結果をDOMに追加することを考えた場合、対象となるノードの子の数や、追加された要素のinnerHTML等を評価すれば良いのではないでしょうか。
まとめ
- テストは習慣付け
- 簡単なものならブラウザだけでも出来る(JS)
- テストコードもメンテナンスを
- ロジックを上手く切り離せればテストも書きやすい
テストに興味が湧いた方は、テスト用のフレームワーク等もありますので、色々触ってみると良いかもしれません。