UrlBuilderの例のTDD

UrlBuilderの例のTDD



この記事では、シンプルだが重要な例を使用したテストを通じて、開発手法の適用についてお話したいと思います。 私が注目したいこのアプローチの主な利点は、開発の開始時にすでに何を開発すべきかを知っていることであり、コードが機能するための非常に具体的な基準があります。



プロトコル、ホストなど、指定されたパラメーターに従ってURLを作成するビルダーを開発します。 想像力に欠ける結果として、実際のWebサービスとのすべての偶然の一致を考慮するようお願いします。



問題



少し前に、Android用のかなり大きなアプリケーションの開発を終えました。 この記事はそれに関するものではないので、詳細には触れません。アプリケーションはファットクライアントであるとしか言えません。 アプリケーションがアクセスするWebサービスには、GETメソッドによって渡されるパラメーターが非常に多くなります。 たとえば、データベース全体を検索するには:



http://server.com?action=search&query=%20







ドキュメントタイプによるフィルタリングですべてのデータベースを検索するには:



http://server.com?action=search&query=%20&document_type_id=N







特定のドキュメントを検索するには:



http://server.com?action=search&query=%20&document_id=M







などなど。 数十個のクラスの可能なクエリ。 さらに、各リクエストは、本番サーバーまたは開発サーバーのいずれかに送信できます。



このWebサービスにアクセスするためのURLの構成方法に関する質問が発生しました。 iPhoneプログラマーが行ったように、特定の要求ごとにマスクを保存するには、同じ問題を解決することは明らかに選択肢ではありません。 1つのパラメーターを追加する場合、数十行を編集する必要があります。 一般的に、自分ですぐに自分でやらないと、不満を抱いているユーザーによって正しくやらざるを得なくなります。



Uri.Builderは、ホストポートを設定する方法がないという理由だけで、適切ではありません。



そこで、URLを作成するクラスを作成することにしました。



分析



まず、クラスに何を望むかを決めますか? Webサービスにアクセスしてjava.lang.Stringとして返すことができるURLを設計してほしい。 プロトコル、ホスト、パス、パラメーターを順番に示し、事前設定(たとえば、データベース全体、特定のドキュメントなどを検索するためのURL)に基づいて、最初からURLを作成できるようにしたいと思います。



開発:クラスインターフェイスと単体テスト



これらの要件のうち、少なくとも2つのクラスを区別できます。 1つ目は任意のURLを作成し、2つ目はいくつかの定義済みクラスからURLを作成します。 これは、クラスのインターフェースを説明するのに十分です。 明らかに、ビルダーと流fluentなインターフェイスパターンを使用します。



 public class UrlBuilder { public UrlBuilder protocol(String protocol){ return this; } public UrlBuilder host(String host){ return this; } public UrlBuilder port(String port){ return this; } public UrlBuilder path(String path){ return this; } public UrlBuilder param(String key, String value){ return this; } public String build() { return null; } }
      
      







 public class ServerUrlBuilder extends UrlBuilder{ public UrlBuilder fullSearch(String query){ return this; } public UrlBuilder fullSearch(String query, int documentTypeId){ return this; } public UrlBuilder documentSearch(String query, long documentId){ return this; } }
      
      







そしてすぐにユニットテストを書きます。 UrlBuilderの最初の...



 public class UrlBuilderTest extends TestCase { public void testRootUrl(){ UrlBuilder builder = new UrlBuilder(); String expected = "http://server.com/"; String actual = builder1.protocol(UrlBuilder.HTTP).host("server.com").build(); assertEquals(expected, actual); } public void testRootUrlWithOneParam(){ UrlBuilder builder = new UrlBuilder(); String expected = "http://server.com/?a=b"; String actual = builder.protocol(UrlBuilder.HTTP).host("server.com").param("a","b").build(); assertEquals(expected, actual); } public void testFullUrl(){ UrlBuilder builder = new UrlBuilder(); String expected = "http://server.com/folder/?a=b"; String actual = builder.protocol(UrlBuilder.HTTP).host("server.com").path("folder").param("a","b").build(); assertEquals(expected, actual); } }
      
      







...そして、ServerUrlBuilderの場合。



 public class SereverUrlBuilderTest extends TestCase { public void testFullSearch(){ ServerUrlBuilder builder = new ServerUrlBuilder(); String expected = "http://server.com?action=search&query= "; String actual = builder.fullSearch(" "); assertEquals(expected, actual); } public void testFullSearchWithDocumentTupeId(){ ServerUrlBuilder builder = new ServerUrlBuilder(); String expected = "http://server.com?action=search&query= &document_type_id=123"; String actual = builder.fullSearch(" ",123); assertEquals(expected, actual); } public void testDocumentSearch(){ ServerUrlBuilder builder = new ServerUrlBuilder(); String expected = "http://server.com?action=search&query= &document_id=123456"; String actual = builder.documentSearch(" ",123456); assertEquals(expected, actual); } }
      
      







完全なカバレッジを得るには、さまざまな組み合わせに対してさらに12個のテストを記述し、パラメーターの1つが入力されていないか、誤った値が入力されている場合の否定的なシナリオをチェックするとよいでしょう。 しかし、親愛なる読者はそのようなコードのシートを許さないかもしれません。



テストを実行します。 もちろん、それらのどれも渡されません。 しかし、今ではコードが機能するための明確な基準があります。 数行追加するだけです。



実装



UrlBuilderクラスに定数を追加します-urlで使用されるいくつかのプロトコルと区切り文字。 各メソッドのロジックをプログラムします。



 public class UrlBuilder { public static final String HTTP = "http"; public static final String HTTPS = "https"; public static final String FTP = "ftp"; private static final String sProtocolHostSeparator = "://"; private static final String sPathSeparator = "/"; private static final String sParamSeparator = "?"; private static final String sParamEquals = "="; private static final String sParamConcotinator = "&"; private String mProtocol; private String mHost; private final ArrayList<String> mPath = new ArrayList<String>(); private final ArrayList<String> mParamKeys = new ArrayList<String>(); private final ArrayList<String> mParamValues = new ArrayList<String>(); public UrlBuilder(){} public UrlBuilder protocol(String protocol){ mProtocol = protocol; return this; } public UrlBuilder host(String host){ mHost = host; return this; } public UrlBuilder path(String path){ mPath.add(path); return this; } public UrlBuilder param(String key, String value){ mParamKeys.add(key); mParamValues.add(value); return this; } public String build() { final StringBuilder builder = new StringBuilder(); builder.append(mProtocol); builder.append(sProtocolHostSeparator); builder.append(mHost); builder.append(sPathSeparator); for(String path : mPath){ builder.append(path); builder.append(sPathSeparator); } if(mParamKeys.size()>0){ builder.append(sParamSeparator); for(int i=0; i<mParamKeys.size(); i++){ String key = mParamKeys.get(i); if(i!=0){ builder.append(sParamConcotinator); } String value = mParamValues.get(i); builder.append(key); builder.append(sParamEquals); builder.append(value); } } String result = builder.toString(); return result; } }
      
      







ユニットテストを再度実行します。 やった、そのうちのいくつかは成功している。 ServerUrlBuilderのロジックを実装することは残っています。



 public class ServerUrlBuilder extends UrlBuilder{ public static final String SERVER_HOST = "server.com"; public static final String PARAM_ACTION = "action"; public static final String ACTION_SEARCH = "search"; public static final String PARAM_QUERY = "query"; public static final String PARAM_DOCUMENT_TYPE_ID = "document_type_id"; public static final String PARAM_DOCUMENT_ID = "document_id"; public UrlBuilder fullSearch(String query){ protocol(HTTP); host(SERVER_HOST); param(PARAM_ACTION, ACTION_SEARCH); param(PARAM_QUERY, query); return this; } public UrlBuilder fullSearch(String query, int documentTypeId){ protocol(HTTP); host(SERVER_HOST); param(PARAM_ACTION, ACTION_SEARCH); param(PARAM_QUERY, query); param(PARAM_DOCUMENT_TYPE_ID, documentTypeId); return this; } public UrlBuilder documentSearch(String query, long documentId){ protocol(HTTP); host(SERVER_HOST); param(PARAM_ACTION, ACTION_SEARCH); param(PARAM_QUERY, query); param(PARAM_DOCUMENT_ID, documentId); return this; } }
      
      







テストを再度実行します-すべて合格します。 これは、必要なものを正確に実装したことを示しています。 もちろん、テストが完全で正確である限り。



おわりに



この記事では、TDD方法論を使用してUrlBuilderクラスとその派生ServerUrlBuilderを実装しました。 問題の検出、分析、テストの作成、最後に実装のすべての開発段階が示されました。 もちろん、さらに多くのことを仕上げる必要があります。 たとえば、配列の転送をパラメーターとして、特殊文字のUrlEncodeを追加できます。 Stringだけでなく、URIとUriも構築するのは素晴らしいことです。 しかし、これはこの記事の範囲を超えた別個の会話です。



All Articles