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/

2011年7月10日日曜日

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

前回、データストアの単一のプロパティを探索して、
値の該当するエンティティを取得する機能を提供する、
EntitySearchという名前の内部クラスを定義して、
コンストラクタを作成しました。

・・・が、訂正があります。
コンストラクタの引数が
・DatastoreService
・String (探索するプロパティ名)
の2つとなっていましたが、
もう1つ追加して
・String (取得するエンティティのカインド名)
の3つとします。
public EntitySearch(DatastoreService ds, String propertyName)
から、
public EntitySearch(DatastoreService ds, String kindName, String propertyName)
とします。
探索するエンティティの種類をkindNameで指定したカインドの
エンティティに絞るためです。

ここでコンパイルエラーがもう一つ残っているので、
そちらを片付けましょう。コンストラクタの実装は後回しです。

コンパイルエラーとなっている行は、
List entitiesList = search.getEntity(propertyValues[i]);
で、getEntity()メソッドが未定義、となっています。

クラスEntitySearchにメソッドgetEntity()を定義します。
public List<Entity> getEntity(String string) {
   return null;
  }
とします。
これでコンパイルエラーはなくなりました。





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

2011年7月8日金曜日

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

前回は、getEntity()メソッドの呼び出しを記述したところ、
メソッドが宣言されていないというコンパイルエラーが出た、
という所まででした。

そこで、getEntity()メソッドを宣言すれば・・・という前に、
このテストクラスを作成する目的に立ち戻ってみます。

そもそも、条件に合致するエンティティを検索する機能を持つ
クラスを作成することが目的です。
getEntity()メソッドは、その目的どおりの機能を持つ(予定の)メソッドですが、
このままメソッドを宣言するのでは、
LocalUnitTestSampleクラスのメソッドとなってしまいます。

それでは、ここで目的の機能を持つクラスを定義しましょう。

LocalUnitTestSampleクラスとは別に.javaファイルを作成するのも
もちろん正しいのですが、
ここでは内部クラスを作成します。
クラスの実装ができてから、別のjavaファイルを作成して
内部クラスから独立させてもかまいません。

作成してみましょう。
まずはgetEntity()メソッド呼び出しの部分です。
List<entity> entitiesList = getEntity(propertyValues[i]);

から、
EntitySearch search = new EntitySearch(ds,propertyName);
List<entity> entitiesList = search.getEntity(propertyValues[i]);

と変更します。
変更後の2行でコンパイルエラーとなります。

まずは1行目です。
EntitySearchという型が解決できない
というメッセージなので、
クラスを宣言します。

public class EntitySearch {
}
そして、コンストラクタが未定義なので、
public EntitySearch(DatastoreService ds, String propertyName) {

  }

を定義します。




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

2011年7月4日月曜日

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

前回は、
doTest()メソッドの中から、
別クラスとして実装する部分をコメントアウトしました。
コメントアウトした部分の行っていたことは、
データストアから条件に沿って取得したエンティティを格納した
java.util.Listオブジェクトを取得することでした。

ここでコンパイルエラーとなった部分は、
entitiesList
です。
ローカル変数entitiesListは宣言された部分が
コメントアウトされたのですから、当然です。

コンパイルエラーを解決するために、
変数entitiesListを宣言します。
コメントアウトの直後、
assertEquals(1,entitiesList.size());
の前に

List entitiesList;


を加えます。

すると、またもやエラーになります。
「変数entitiesListが初期化されていない可能性があります。」
というメッセージです。

変数entitiesListは、データストアから取得したエンティティを格納した
java.util.Listオブジェクトとしたいのですから、
目的の機能を持ったメソッドを宣言します。

List entitiesList = getEntity(propertyValues[i]);


とします。
すると、メソッドgetEntity(String)は未定義なので、
コンパイルエラーになります。


ここまでのコードは、以下の通りです。


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


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

2011年7月3日日曜日

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

前回、テストメソッドの中身を別クラスに移す
と書きました。

そのテストメソッドは以下の通りでした。


@Test
 public void doTest(){
  //サンプルデータをデータストアから検索する
  for (int i = 0;i < propertyValues.length;i++){
   Query query = new Query(DATA_KIND);
   query.addFilter(propertyName, FilterOperator.EQUAL, propertyValues[i]);
   PreparedQuery prepare = ds.prepare(query);
   List entitiesList = prepare.asList(FetchOptions.Builder.withDefaults());
   assertEquals(1,entitiesList.size());
   Iterator iter = entitiesList.iterator();
   while (iter.hasNext()){
    Entity entity = iter.next();
    assertTrue(propertyValues[i].equals(entity.getProperty(propertyName)));
   }
  }
 }
まず、別クラスに移す部分をコメントアウトしてしまいましょう。
コメントアウトする部分、つまり別クラスの機能とする部分は、
Queryを生成して、データストアからプロパティの値が該当するエンティティの
リストを取得する部分です。

@Test
 public void doTest(){
  //サンプルデータをデータストアから検索する
  for (int i = 0;i < propertyValues.length;i++){
/*   Query query = new Query(DATA_KIND);
   query.addFilter(propertyName, FilterOperator.EQUAL, propertyValues[i]);
   PreparedQuery prepare = ds.prepare(query);
   List entitiesList = prepare.asList(FetchOptions.Builder.withDefaults());
*/   assertEquals(1,entitiesList.size());
   Iterator iter = entitiesList.iterator();
   while (iter.hasNext()){
    Entity entity = iter.next();
    assertTrue(propertyValues[i].equals(entity.getProperty(propertyName)));
   }
  }
 }

すると、
assertEquals(1,entitiesList.size());
Iterator iter = entitiesList.iterator();
の行でコンパイルエラーとなります。




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

2011年7月1日金曜日

データストアAPIを利用するテスト駆動開発をおこなってみる

前回まで、サンプルコードの修正を行いました。
データストアに保存するサンプルデータを変更した後の
コードは前回掲載したとおりです。

今後は、修正後のサンプルコードを

サンプルコード2

と記述することにします。

サンプルコード2でJUnitテストを行うと、
きちんとテストは成功します(確認しました)。

しかし、サンプルコード2のテストは
テストコードではあるものの、具体的に何かのJavaクラスを
テストしたわけではありません。
単に、データストアにデータを保存して、
保存したデータを取り出してみた、というだけです。

一連の動作を行うだけならば、
わざわざJUnitテストクラスを作るのではなく、
普通のJavaクラスにmainメソッドを宣言して
実装すればいいことです。

JUnitテストで動作するコードを書いた理由は、
きちんと動作するということが視覚的に確認できる、
という意味があります。
要するに、バーが緑になる(テストが全て成功する)ということです。

それだけではありません。
成功するテストがあるならば、テストメソッドの内部で行っている処理を
別のクラスの機能として実装することができます。
新しく実装するクラスのテストが既にできていることになります。

次回から、
実際にテストメソッドの内部の処理を別クラスに移す、
ということを行ってみます。




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