このエントリを書こうと思った背景について
一週間位前にjasmine でテストを書くモチベーションが高まったことをtweetしてましたが、こんなことを」つぶやいていたように非同期処理のテストの書き方がわからずにはまってました。
jasmine-titanium のテストケースの書き方が悪いせいか、QiitaのAPIのログイン必須のURLエンドポイントに対してGETリクエストを投げる所がループしてるみたいで、Rate limit exceededになってるんだ。
— hiroshi oyamada (@h5y1m141) December 24, 2012
Titanium+jasmineな環境で非同期処理のテストケース書いてるんだけど、waitsFor()が意図したように待ってくれないなぁ。。書き方間違ってると思えないので切り分けのためにnode.jsで同じテスト書いてみよ。そっちでパスするならTitanium固有の問題になりそう
— hiroshi oyamada (@h5y1m141) December 26, 2012
毎日のように、"jasmine" "非同期" "async" "node-jasmine"というキーワードでググっりまくり、やっと解決出来ました。
ひげろぐさんがTitaniumの非同期なイベントのテストというエントリでTitanium+jasmineで、イベントに関するテストについてまとめられてますが、自分が調べる限り、非同期通信についてまとめてるブログ等がなかったので、
- ドツボにはまった要因
- 解決方法(jasmine.async 導入で解決)
という観点でまとめておきます
ドツボにはまった要因
結論:jasmineのwaitsFor()とruns()の使い方を誤解していた
結論を先に書くと、jasmineのwaitsFor()とruns()の使い方を誤解していたことに起因してたので、1週間近く無駄な時間を過ごしました。
jasmineのwaitsFor()とruns()について、自分が期待していた動作というのは
- waitsFor()の中で非同期通信の処理を実施
- 非同期通信の処理が完了した段階で、runs()が実行
- runs()内のexpect()が呼ばれてテストが評価される
というものでした。
そのためテストをこんな風に書きました。
describe 'Qiitaクラスのためのテスト', -> beforeEach -> Qiita = require('qiita') qiita = new Qiita() it '最終ページに到達した場合の処理', -> lastURL = "https://qiita.com/api/v1/stocks?page=10&token=MYTOKEN" waitsFor -> qiita.getNextFeed(lastURL, (result,links) -> return result ) runs -> expect(result).toBe null
これを実施すると、ターミナル上ではtimeoutになった旨のメッセージが流れてるし、https://qiita.com/api/v1/stocks?page=10&token=MYTOKENにアクセスすると「Rate limit exceeded」のErrorが返ってくるわで、全く意図したことができてませんでした。。
waitsFor の中で書いた非同期通信の処理を待ってくれるわけではなかった
これ以降、ひたすら、"jasmine" "非同期" "async" "node-jasmine"というキーワードでググってヒットしたページをたくさん見ていく中で、Jasmineで書く遅延処理のテストという記事が目に止まりました。
ひとまずこのコードをコピペして、Titanium上でbuildしてみたら、ターミナル上に
false false false : :
としばらくfalseが繰り返し表示されました。
上記記事中でも
waitsForの第一引数のfunctionを、timeout時間分絶えず実行し、trueが返れば続くrunsを実行
と書かれてるように、timeout時間分絶えず実行されており、ここではじめてwaitsFor()とruns()について自分が誤解してることに気づきました。
jasmineのdone()という存在を知る
その後も、"jasmine" "非同期" "async" "node-jasmine"というキーワードでググる日々が続き、きっかけは忘れたのですがdone()の存在に気づきました
ひとまず使い方になれる目的で、QiitaのAPIにアクセスするのではなく、twitterのpublic timelineにアクセスする以下のようなテストを書いてみました。
describe '非同期処理の確認テスト', -> it 'twitter APIにアクセスして件数取得', (done)-> waits(500) xhr = Ti.Network.createHTTPClient() xhr.open("GET","http://search.twitter.com/search.json?q=Android") xhr.onload = -> twitter = JSON.parse xhr.responseText expect(twitter.results.length).toBe 15 done() xhr.send()
これでOKかと思って、
describe 'Qiitaクラスのためのテスト', -> beforeEach -> Qiita = require('qiita') qiita = new Qiita() it '投稿情報の件数が一致する', () -> qiita.getMyStocks( (result,links) -> expect(result.length).toBe 20 done() )
みたいな形にしたのですが、どうもテストが実行されない感じで意図した動作になりませんでした。
※結局これについては原因はわからないので、気が向いたらこの部分もう少し深掘りするかも
解決方法
最終的にはjasmine.async 導入で解決
その後も、ひたすら、"jasmine"関連のキーワードでググる日々が続き、Jasmine.Async: Making Asynchronous Testing With Jasmine Suck Lessというサイトを見つけました。
そこで
Just add the parameter to the callback, and call “done()” when the async code has completed
という記述を見つけて、
「うん?これはもしかしたら使えるかも」
と思い上記のjasmine.async.jsをダウンロードして以下のように配置しました
Resources │ ├── test │ │ ├── items.json │ │ ├── lib │ │ │ ├── jasmine-1.0.2.js │ │ │ ├── jasmine-titanium-console.js │ │ │ ├── jasmine-titanium.js │ │ │ └── jasmine.async.min.js
その上で、以下のようにテストを書いたら、無事にテスト実行されました!!
describe 'Qiitaクラスのためのテスト', -> beforeEach -> Qiita = require('qiita') qiita = new Qiita() describe 'QiitaのStock取得', -> content = null async = new AsyncSpec(@) async.beforeEach (done) -> runs -> qiita.getMyStocks( (result,links) -> content = result done() ) waits 1000 it '投稿情報取得出来る', ()-> runs -> expect(content).not.toBeNull() it '投稿情報の件数が一致する', () -> runs -> expect(content.length).toBe 20
ほんと、長い道のりでした ^_^;
これでやっとjasmineで色々なテストケースが書けるようになったので、年明けからガンガンテスト書いていこうと思います