2011年7月30日土曜日

テスト駆動開発の利点(その2)

前回では、サンプルコードのテスト駆動開発で
・コンパイルエラーまたはテストの失敗
↓
・エラーの解消
↓
・コンパイルエラーまたはテストの失敗
というサイクルが繰り返されることで、
コードの開発が促進されることを述べました。

これの特徴は、
「エラーがコーディングの区切りとなる」
ということです。

エラーが出たら解消しなければいけない、
と考えるのは普通ですが、
テスト駆動開発では
コーディングの進捗を表します。
ここまでは出来た、というわけです(後で手直しするハメになることも当然ありますけど)。

例えば、コーディングの途中で休憩したくなった時に、
エラーの出たところで休憩すれば、戻ってきても
どこから続ければいいのかすぐ分かります。
休憩しておしゃべりなんかしている間に
どこまで仕事が進んでいたのか忘れてしまって
再開地点を探すことから始めなければならなかった、
なんてことはありませんか?私はありますけど。それもよく。

忘れたって大丈夫なんです。
コンパイルエラーやテストの失敗は、
開発しているマシン(コンパイラやIDEなど)が教えてくれます。
これで、仕事の栞を頭の中から外して、
休憩に集中できますね(それも変)。

仕事と休憩のメリハリも大切ですね、ということにします。



インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/

2011年7月29日金曜日

テスト駆動開発の利点

前回まで、Google App Engine for Java のAPIを利用するクラスの
テスト駆動開発を試してみました。

あくまで、ここで行った方法は
私なりのやり方です。他の方法で行っている方は沢山いるでしょうし、
異論もあるでしょう。

先に動作確認済みのサンプルコードがある状態から
新しいクラスを作る、という例なので
そのままのやり方で応用できるというわけでもありません。
メソッドの中身をコピーしてくる部分などは、
先に機能の実装が出来ていなければなりませんし、
コンストラクタの引数を先に決めてから定義している部分も
かなり実装のイメージが出来上がっていなければ出来ません
(ここについては、コンストラクタの引数を定義しないでおいて、
必要になってから定義していくやり方もあるのですが、
つい、飛ばしてしまいました)。

しかし、既に定義されている仕様に従ってテストコードを
作成してから、そのテストに通るクラスを実装していくという方法は
有効なものです。

前々回に述べたように、テストコードが実装の指針を示していくので
コードの記述が楽になります。
実装の流れは、
・コンパイルエラーまたはテストの失敗
↓
・エラーの解消
↓
・コンパイルエラーまたはテストの失敗
という繰り返しになっています。
コンパイルエラーが何度も何度も繰り返されるので
まとめてエラーが出ないコードを書けばいい、というのとは違います(間違っているという意味ではありません)。
エラーが次に実装すべきコードを示している、ということです。
コードを頭の中からひねり出す苦労を減らせます。

インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/

2011年7月25日月曜日

Google App Engine for Java のデータストアAPIを利用するクラスをテスト駆動開発してみる(その10)

先に進む前に、ソースコードを掲載します。

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;

/**
 * Google App Engine for Javaにおけるユニットテストのサンプルコード。
 */
public class LocalUnitTestSample {

 private static final String DATA_KIND = "sampleDataKind";

 private LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

 //private String[] propertyNames = {"pName1","pName2","pName3","pName4","pName5"};
 private String propertyName = "propertyName";

 private String[] propertyValues = {"pValue1","pValue2","pValue3","pValue4","pValue5"};

 private DatastoreService ds;

 private List<key> keyList;

 /**
  * @throws java.lang.Exception
  */
 @Before
 public void setUp() throws Exception {
  helper.setUp();

  //サンプルデータをデータストアに保存する
  putSampleData();
 }

 /**
  * サンプルデータをデータストアに保存するメソッド。
  */
 private void putSampleData() {
  keyList = new ArrayList<key>();
  ds = DatastoreServiceFactory.getDatastoreService();
  for (int i = 0; i < propertyValues.length; i++) {
   Entity entity = new Entity(DATA_KIND);
   entity.setProperty(propertyName, propertyValues[i]);
   Key key = ds.put(entity);
   keyList.add(key);
  }
 }

 /**
  * @throws java.lang.Exception
  */
 @After
 public void tearDown() throws Exception {
  //サンプルデータをデータストアから削除する
  deleteSampleData();

  helper.tearDown();
 }

 /**
  * サンプルデータをデータストアから削除するメソッド。
  */
 private void deleteSampleData() {
  Iterator<key> iter = keyList.iterator();
  while (iter.hasNext()){
   ds.delete(iter.next());
  }
 }

 @Test
 public void doTest(){
  //サンプルデータをデータストアから検索する
  for (int i = 0;i < propertyValues.length;i++){
   EntitySearch search = new EntitySearch(ds,DATA_KIND,propertyName);
   List<entity> entitiesList = search.getEntity(propertyValues[i]);
   assertEquals(1,entitiesList.size());
   Iterator<entity> iter = entitiesList.iterator();
   while (iter.hasNext()){
    Entity entity = iter.next();
    assertTrue(propertyValues[i].equals(entity.getProperty(propertyName)));
   }
  }
 }

 /**
  * データストアから、設定された名前のプロパティが該当する
  * エンティティを取得する機能を提供するクラス。
  */
 public class EntitySearch {

  /**
   * データストアサービスと、探索するプロパティの名前を引数とするコンストラクタ。
   * @param ds データストアサービスオブジェクト
   * @param kindName 取得するエンティティのカインド名文字列
   * @param propertyName 探索するプロパティ名を表す文字列
   */
  public EntitySearch(DatastoreService ds, String kindName, String propertyName) {

  }

  /**
   * データストアからエンティティを検索するメソッド。
   * コンストラクタで設定したカインド、プロパティ名の値が
   * 引数propertyValueと同一のエンティティを格納したリストを返します。
   * @param propertyValue 検索条件となるプロパティの値オブジェクト
   * @return コンストラクタで設定したプロパティが引数propertyValueと同一の値のエンティティのリスト
   */
  public List<entity> getEntity(Object propertyValue) {
   Query query = new Query(DATA_KIND);
   query.addFilter(propertyName, FilterOperator.EQUAL, propertyValue);
   PreparedQuery prepare = ds.prepare(query);
   List<entity> entitiesList = prepare.asList(FetchOptions.Builder.withDefaults());
   return entitiesList;
  }

 }

}


インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/

2011年7月23日土曜日

Google App Engine for Java のデータストアAPIを利用するクラスをテスト駆動開発してみる(その9)

前回で、EntitySearchクラスの実装が出来ました。

テストを実行してみます。

・・・成功しました。バーが緑になっています。

9回に渡ってテスト駆動開発を行ってきましたが、
とりあえず今回で終了です。
簡単な機能を持つクラスの作成でしたが、
 一応の完成をみました。

全体の流れをまとめてみると、
1.テストコードの作成
2.テスト対象のクラスを定義する
3.コンパイルエラーを修正しながら実装
4.テストの実行
5.エラーの修正
6.3~5を繰り返す
という順で開発を行っています。

1.テストコードの作成
ここでは、開発対象となるクラスの仕様に沿って、
テストを作成します。
メソッドのテストならば、特定の引数をメソッドに与えると、
結果が仕様通りに返ってくることを検証します。
条件と結果の組み合わせでテストするわけです。

今回は、動作するサンプルとして書いたテストコードを元に
開発を行ったので、開発対象のクラスに移したい機能を
コメントアウトする、というやり方で開発しています。
しかし、事前に機能するコードが実装されていなくても、
実装したい機能をテストするコードを書くことで、
開発の指針がコンパイルエラーやテストの失敗として
示されます。




インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/

2011年7月21日木曜日

Google App Engine for Java のデータストアAPIを利用するクラスをテスト駆動開発してみる(その8)

コンパイルエラーが解消されたところで、
テストを実行してみます。

結果は、
NullPointerException
が返ってきます。

EntitySearch.getEntity()メソッドのreturn文が、
return null;
となっているのだから、当然です。

せっかくgetEntity()メソッドにコメントアウトしていた
機能をコピーしてきたのですから、
その結果を返しましょう。

先ほどのreturn文を、
return entitiesList;
と変更します。




インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/

2011年7月16日土曜日

Google App Engine for Java のデータストアAPIを利用するクラスをテスト駆動開発してみる(その7)

EntitySearch.getEntity(Object)メソッドが
単純にnullを返すだけになっているために、
テストを実行するとdoTest()メソッド内で
NullPointerExceptionがスローされる、
という所まで、前回は進みました。

getEntity()メソッドの中身を実装するのは簡単、
と前回の終わりに述べました。
同時に、既に大体出来ている、とも述べました。

テスト駆動開発を始めるときに、
doTest()メソッド内でコメントアウトした部分がありました。
開発するクラスの動作部分、テストする対象を
コメントアウトしたのですが、
まさにこの部分がgetEntity()メソッドの行いたい事を
表しています。
コメントアウトした部分の代わりに
EntitySearchオブジェクトを使った部分を記述しました。

それでは、コメントアウトした部分を
getEntity()メソッドにコピーします。

public List<entity> getEntity(Object propertyValue) {
   Query query = new Query(DATA_KIND);
   query.addFilter(propertyName, FilterOperator.EQUAL, propertyValues[i]);
   PreparedQuery prepare = ds.prepare(query);
   List<entity> entitiesList = prepare.asList(FetchOptions.Builder.withDefaults());
   return null;
  }

またもや、コンパイルエラーが出ます。
ループカウンタを表していた
i
という変数が解決できないという内容です。
query.addFilter()メソッドの第3引数に与えるのは、
比較対象となるプロパティ値なので、
これはgetEntity()メソッドの引数propertyValueに変更します。
これで「i」という変数はメソッド内からなくなり、
コンパイルエラーは解消されます。




インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/

2011年7月14日木曜日

Google App Engine for Java のデータストアAPIを利用するクラスをテスト駆動開発してみる(その6)

前回で、コンパイルエラーは解決しました。
コード全体を見れば未完成なのは明らかですが、
とりあえず、テストを実行してみましょう。

テストの実行結果は、
java.lang.NullPointerExceptionがスローされて失敗します。

場所は、
doTest()メソッド内、
assertEquals(1,entitiesList.size());
です。
entitiesListがnullなので、size()メソッドを呼び出せないわけです。

その前の行でentitiesListにsearch.getEntity(propertyValues[i])
を代入しているわけですが、
そのsearch.getEntity()メソッドが
return null;

なのですから、当然の結果です。

そこで、getEntity()メソッドを実装します。
難しくはありません。
この記事は行き当たりばったりで書いていますが、
はっきり言えます。
難しくはありません。

なぜなら、既に大体出来ているからです。




インストール不要・無料のKaede翻訳ツール:
http://kaedetrans.appspot.com/