SiNBLOG

140文字に入らないことを、極稀に書くBlog

Slim3 Source Code Reading No11 Mock

Slim3 Source Code Reading #11 - ChugokuGTUG

に参加してきました!


今回も@ttyokoyama 殿と2人だったので、まったりと進みました。
まぁ、他の方が参加していても、まったり進むのですがw

T.Yokoyamaのブログ: Slim3 Source Code Reading #11


今回はtesterの中にあるMockたちです。
数はそこそこあるのですが、どれもGAEに関係しているわけではなく、ServletのRequestやResponseのMockでした。
その中に、UrlFetchHandlerというインタフェースがありました。
こいつはURLFetchServiceをテストするためのもののようで、responseを自分で作ってやれるようです。


http://code.google.com/intl/ja/appengine/docs/java/urlfetch/
公式のURLフェッチのところには、URLFetchServiceのサンプルコードはないようです。
また、こいつが本気を出すところは、非同期でURLFetchを行う時のようです。
URLFetchService#fetchAsync()を使うとGAE環境が用意しているスレッドで、サーブレットのスレッドとは別に非同期でHTTP通信を行ってくれます。


twitter4jを使う時も有効のようで、山本祐介殿のBlogでも触れられてました。
http://samuraism.jp/diary/2011/07/06/1309912740000.html

また、実際の使い方は以下のBlogに詳しく書いてあります。
今回作成したサンプルも、こちらを参考にしました。
非同期URLFetch、FetchAsync()の使い方【Google App Engine】


それでは、ソースコードです。
IndexController


public class IndexController extends Controller {

/**
* Twitter検索APIをFetchする
*/
@Override
public Navigation run() throws Exception {
final String TWITER_SEARCH_URL =
"http://search.twitter.com/search.json?q=chugokugtug";
String tweetJson = fetchUrl(TWITER_SEARCH_URL, "UTF-8");

List tweetList = buildeTweetList(tweetJson);

requestScope("tweetList", tweetList);
return forward("index.jsp");
}

/**
* 指定したURLをFetchする
*
* @param urlStr
* @param encoding
* @return
* @throws IOException
*/
private String fetchUrl(String urlStr, String encoding) throws IOException {
URLFetchService service = URLFetchServiceFactory.getURLFetchService();
URL url = new URL(urlStr);
HTTPResponse res = service.fetch(url);
return new String(res.getContent(), encoding);
}

/**
* Twitter検索APIの結果から、TweetListを作る
*
* @param searchJson
* @return
*/
private List buildeTweetList(String searchJson) {
List tweetList = new ArrayList();
try {
Gson gson = new Gson();
JsonElement element = gson.fromJson(searchJson, JsonElement.class);
JsonObject json = element.getAsJsonObject();
JsonArray array = json.getAsJsonArray("results");
for (int i = 0; i < array.size(); i++) {
Object obj = array.get(i);
if (obj instanceof JsonObject) {
tweetList.add((JsonObject) obj);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return tweetList;
}
}

テスト用に、TwitterSearchAPIを呼んでいるのですが、むしろ取得した後のJSONの処理の方が長くなってしまった・・・。
URLFetchServiceを使っているのは、fetchUrl()なので、4行しかありません。


上記のControllerをテストするコードです。
IndexControllerTest


public class IndexControllerTest extends ControllerTestCase {

/**
* TwitterSearchFetchHandlerを使ったテスト
*
* @throws Exception
*/
@Test
public void testUrlFetch() throws Exception {
tester.setUrlFetchHandler(new TwitterSearchFetchHandler());

tester.start("/urlfetch/");
IndexController controller = tester.getController();
List tweetList = tester.requestScope("tweetList");

assertThat(controller, is(notNullValue()));
assertThat(tester.isRedirect(), is(false));
assertThat(tester.getDestinationPath(), is("/urlfetch/index.jsp"));
assertThat(tweetList, is(notNullValue()));

JsonObject jsonObject = tweetList.get(0);
assertThat(jsonObject.get("id").toString(), is("177727132691214338"));
assertThat(jsonObject.get("from_user").toString(), is("\"sinmetal\""));
assertThat(
jsonObject.get("text").toString(),
is("\". @ttyokoyama http://t.co/POuEYdnZ 殿のコードを検証する内容でBlogを書こうとしたが、URLFetchService よりも、jsonと戦っているというw #chugokugtug\""));
}
}

テストのためのデータを、実際のデータからコピーしてきたので、僕のツイートになってますが、こんな感じです。
キモは一番最初にやってるところです。


tester.setUrlFetchHandler(new TwitterSearchFetchHandler());
TwitterSearchFetchHandlerがテスト用に作ったUrlFetchHandlerを実装しているクラスです。
中身はTwitterSearchAPIを呼んだ時と同じ内容のJSONを返しているだけです。
これでUnitTestで毎回同じ値が返ってくるようにできました。


TwitterSearchFetchHandler


public class TwitterSearchFetchHandler implements URLFetchHandler {

public byte[] getContent(URLFetchRequest request) throws IOException {
String json =
"{\"completed_in\":0.084,\"max_id\":177727132691214338,\"max_id_str\":\"177727132691214338\",\"next_page\":\"?page=2&max_id=177727132691214338&q=chugokugtug&rpp=1\",\"page\":1,\"query\":\"chugokugtug\",\"refresh_url\":\"?since_id=177727132691214338&q=chugokugtug\",\"results\":[{\"created_at\":\"Thu, 08 Mar 2012 12:07:05 +0000\",\"from_user\":\"sinmetal\",\"from_user_id\":26903289,\"from_user_id_str\":\"26903289\",\"from_user_name\":\"\u771f\",\"geo\":null,\"id\":177727132691214338,\"id_str\":\"177727132691214338\",\"iso_language_code\":\"ja\",\"metadata\":{\"result_type\":\"recent\"},\"profile_image_url\":\"http://a0.twimg.com/profile_images/1042264867/1277990403846_normal.png\",\"profile_image_url_https\":\"https://si0.twimg.com/profile_images/1042264867/1277990403846_normal.png\",\"source\":\"<a href="http://www.tweetdeck.com" rel="nofollow">TweetDeck</a>\",\"text\":\". @ttyokoyama http://t.co/POuEYdnZ \u6bbf\u306e\u30b3\u30fc\u30c9\u3092\u691c\u8a3c\u3059\u308b\u5185\u5bb9\u3067Blog\u3092\u66f8\u3053\u3046\u3068\u3057\u305f\u304c\u3001URLFetchService \u3088\u308a\u3082\u3001json\u3068\u6226\u3063\u3066\u3044\u308b\u3068\u3044\u3046\uff57 #chugokugtug\",\"to_user\":null,\"to_user_id\":null,\"to_user_id_str\":null,\"to_user_name\":null}],\"results_per_page\":1,\"since_id\":0,\"since_id_str\":\"0\"}";
return json.getBytes("UTF-8");
}

public int getStatusCode(URLFetchRequest request) throws IOException {
return HttpServletResponse.SC_OK;
}

}


他にもErrorが発生した時なんかも作っていけば、ある程度UnitTestできそう!
前回のServletTester#addBlobKey()もマイナーな感じでしたが、今回のURLFetchHandlerもマイナーな感じのようで、ぐぐってもあんまり情報が出てきてくれないですねw


最後に余談だけど、@ttyokoyama 殿にGo言語で作られてるものって何があるんですか?と聞いたら、youtubeで使われてるよ!と以下を教えて下さった。

GitHub - vitessio/vitess: Vitess is a database clustering system for horizontal scaling of MySQL.

先日公開されたばかりで、vitessでぐぐると"もしかしてvitesse"と言われるぐらいだ!
興味がある方は、読んでみると良いのではないでしょうか。


今回の実際のソースはこちら!
IndexController
Google Code Archive - Long-term storage for Google Code Project Hosting.

IndexControllerTest
Google Code Archive - Long-term storage for Google Code Project Hosting.

TwitterSearchFetchHandler
Google Code Archive - Long-term storage for Google Code Project Hosting.