hiromitsuuuuu.log();

to see more to see.

第16回GTUG Girls Meetup 「TDD 超初心者入門ではじめてのペアプロ」で初TDDしてきたよ。

GTUG Girlsで初TDDしてきました!本編に30分ほど遅刻したので、@t_wadaさんのありがたいお話とデモを見れなかったのはとっても残念だったけど、テストを書くのには参加できました。肝心の説明を聞いていないからペアプロはできないかなぁと思っていたけど、@tacamyさんとペアプロできて色々覚えてきました(○´Д`○)

課題

区間を題材に、5個のお題がでました。区間オブジェクト(はじまりとおわりを指定してnewする)と、そのオブジェクトが持つ関数をテストを書きながら作っていくというもの。

https://gist.github.com/twada/3a6b068942810b89679f

課題を例にして、やったこと、理解したことを以下でまとめました。後日@tacamyさんが共有してくださったテストコードを参照させていただきました。

TODOを書く

まず、どんな挙動の関数が必要か考えて、テストの必要性がある項目をテキストでTODOリストとして整理しました。[]にはテストが終わったかどうかのチェックを入れていきます。「x」はテストが終わったやつで、「@」は今書いているやつ。

[x][3,8]の下端点3を返す
[@][3,8]の上端点8を返す

テストを書く

言語はJavaScriptを選んだので、Qunitを使いました。事前に以下をcloneしといてねーってアナウンスがありました。これのtests.jsを真似して書いていきます。

https://github.com/tddbc/javascript_qunit

 

値を比較する

ClosedRangeという区間オブジェクトの、下端点と上端点を取得するメソッドのテストを書いていきました。「生成」すること自体をテストするのはすごく難しいとのこと。取得できる、ということはオブジェクトが生成できていることは自明であるので、「生成」自体はテストしなくていいよ、とのことでした。値の比較のコードは以下。getLowerEndpoint()で、下端点の値(整数)が戻ってくればOKというテスト。

// 第1引数には何のテストか、を書く
test('下端点は3を返す', function(assert) {

  // テストしたいRangeオブジェクトを作る
  var range = new ClosedRange(3, 8);

  // 実行結果(第1引数)と期待値(第2引数)を比較する
  assert.equal(range.getLowerEndpoint(), 3);

});

ClosedRangeをnewしてるけど、この時点ではClosedRangeやgetLowerEndpointなんてものはどこにもありません。テストを実行(test.htmlを実行すると結果が見れる)しても、「ClosedRangeなんてねぇよ!」と怒られるだけです。でも、この段階ではこれが正解。

実装コードを書く

次に実装コードを書いていきます。実装コードはClosedRange.jsとして別ファイルで書いていきます。test.htmlで、テストコードの前に読み込みます。

  <!-- 実装コード -->
  <script src="./ClosedRange.js"></script>
  <!-- Qunit本体 -->
  <script src="./vendor/qunit/qunit.js"></script>
  <!-- テストコード -->
  <script src="./tests.js"></script>

実装コードは、テストを何度か実行しながらちょっとずつ実装していきました。とりあえず、ClosedRangeを用意して、「ClosedRangeなんてねぇよ!」といわれないことを確認したあと、メソッドを追加して、おもむろに答えの「3」を返してみます。

var ClosedRange = function(min, max) {}

ClosedRange.prototype.getLowerEndpoint = function(){
    return 3;
};

失敗していたテストが、今度は成功します。成功したことを確認して、今度はコンストラクタメソッドの中を実装していきました。

var ClosedRange = function(min, max) {
  this.min = min;
  this.max = max;
}

ClosedRange.prototype.getLowerEndpoint = function(){
    return this.min;
};

これでTODOリストの1番目、「[3,8]の下端点3を返す」が終了。やったー(*`・∀・´*)人(´○_`*)ノ

Meetupで使ったQUnitの機能

module

複数のテストで同じ内容のrangeオブジェクトを何度もnewしていたので、moduleで共通化しました。たとえば、こんな感じ。

test('下端点は3を返す', function(assert) {
  var range = new ClosedRange(3, 8);     // このrangeを共通化したい
  assert.equal(range.getLowerEndpoint(), 3);
});

test('上端点は8を返す', function(assert) {
  var range = new ClosedRange(3, 8);     // このrangeを共通化したい
  assert.equal(range.getUpperEndpoint(), 8);
});

moduleを使って以下のようにまとめました。

module('閉区間 [3,8] の場合', {
  setup: function() {
    this.range = new ClosedRange(3, 8);
  }
});

// ここから上記のmoduleが使える
test('下端点は3を返す', function(assert) {
  assert.equal(this.range.getLowerEndpoint(), 3);
});

test('上端点は8を返す', function(assert) {
  assert.equal(this.range.getUpperEndpoint(), 8);
});

thisのコンテキストはwindowではないそう。別のmoduleを定義したい場合(例えば、別の引数でnewしたrangeオブジェクトをテストで使いたい場合)は、続けてmoduleを書けば、それ以降別moduleになるんだそう。

module('閉区間 [3,8] の場合', {
  setup: function() {
    this.range = new ClosedRange(3, 8);
  }
});

// ここではthis.rangeは閉区間[3,8] 

module('閉区間 [3,8] の場合', {
  setup: function() {
    this.range = new ClosedRange(1, 9);
  }
});

// ここではthis.rangeは閉区間[1,9] 

});

別moduleなので、this.rangeが上書きされているわけでもないんだそう。このへんちょっと不思議。どのmoduleを使ってテストしてるか把握していないと、うっかりミスしてしまいそうだなって思った。

真偽値のテスト

真偽値の確認にはequalではなくokをつかう。

test('閉区間が指定した整数を含むか', function(assert) {
  assert.ok(!this.range.contains(2),'[3,8] は 2 を含まない');  // falseを期待する処理は「!」でtrueにする
  assert.ok(this.range.contains(3), '[3,8] は 3 を含む');
});

真偽値とは直接関係無いけど、「閉区間が指定した整数を含むか」のテストを書いているときに、漏れなくテストするのであれば9つ必要だよとアドバイスとしていただきました。[3,8]の区間をテストするなら、境界値のすぐ両隣(2,4,7,9)と、境界値自身(3,8)と間(5)、大きく離れた値(例:負の整数の最大値、正の整数の最大値)など。

例外系のテスト

例外系にはthrowsを使って、中に例外を発生させる処理を書きます。エラーメッセージの内容をテストしたい場合は、try-catchを使ってメッセージをequalで比較します。

test('上端点より下端点が大きい閉区間を作った場合に例外が発生する', function(assert) {
  assert.throws(function() {
      // 例外が発生する処理
      new ClosedRange(9, 1);
  });
});

// エラーメッセージの検証
test('下端点に値が渡されない場合に「下端点が空だよ」というメッセージが返る', function(assert) {
   try {
      new ClosedRange();
   }
   catch (e) {
      assert.equal(e.message, '下端点が空だよ');
   }
});

かんそう

GTUG Girls初参加だったけど、スタッフさんたちが優しくて、遅刻したけど楽しく参加することができました!
OJT時代(4年くらい前?)はわけもわからずJava単体テスト書いてたし、品質の担保のためのテストだったからか、テストファーストでは書いてなかった。今日TDDしてみて、テストコードが実装をサポートしてくれてる感がちょっとあって、TDDの良さってこゆこと?って思いました。テストコードの実装に+α時間がかかってはしまうものの、補助があるみたいで書きやすいし、うっかりした抜け漏れが防げて良さそう。もうちょっと知りたいし余裕のあるところに取り入れてみたいなと思いました。はじめの一歩成功です(。・ω・。)
あと、ペアプロも楽しかったです。一緒に考えたり悩めたり気づかないところ気づいてもらえたり面白い。