40歳からのキャリアチェンジ

20代はエンジニア・PM、30代はWeb系エンジニア向けのキャリアアドバイザー。40代の今はフリーランスで開発含めて色々やってます。技術ネタとしてはRuby/RailsとJavaScript関連あたり

Titanium+jasmineでの非同期通信のテストの書き方が意外と難しかったのでハマりどころと対処方法についてまとめてみました

このエントリを書こうと思った背景について

一週間位前にjasmine でテストを書くモチベーションが高まったことをtweetしてましたが、こんなことを」つぶやいていたように非同期処理のテストの書き方がわからずにはまってました。

毎日のように、"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で色々なテストケースが書けるようになったので、年明けからガンガンテスト書いていこうと思います