Home > JavaScript

JavaScript Archive

【Javascript】QUnit導入と単体テスト–第1回社内勉強会

参加者:@geckotang @cancer6
(たぶん増えない)

QUnit導入

qUnit本体 http://dev.jquery.com/view/trunk/qunit/testrunner.js
CSS http://dev.jquery.com/view/trunk/qunit/testsuite.css
jquery http://jqueryjs.googlecode.com/files/jquery-1.2.6.min.js

メソッド

  • test(name, test_func) … 通常のテスト
  • asyncTest(name, test_func) … 非同期テスト
  • module(name) … テストのグループ化

  • ok(state, message) … stateがtrueならテスト通過、falseならテスト不通過

テストが正常に通過したか、関数が正常に動作しているか、などのチェック

sample

$(function(){
    module('sample1');
    function hoge (x) {
        return x;
    };
    test("example1", function() {
        hoge(10);
        ok( true, "all pass" );
    });
    test("example2", function() {
        _hoge(10);
        ok( true, "all pass" ); //_hoge()は無いので通らない
    });
});

  • equals(actual, expected, message) … 実際の値(actual)が、期待する値(expected)と一致(==)するかどうか
  • same(actual, expected, message) … 実際の値(actual)が、期待する値(expected)と厳密に一致(===)するかどうか

実行結果と期待値とを比較する。たぶんよく使うのはこっち。

sample

$(function(){
    module('sample2');
    function hoge (x) {
        return x;
    };
    test("example1", function(){
        var ho = hoge(10);
        equals( 10, ho, "引数は10でした。" );
    });
});

  • stop() … テスト中断。非同期処理の処理完了待ちなど
  • stop(ms) … (ms後に)テスト中止。非同期処理で一定時間応答がない場合など。タイムアウト
  • start() … テスト再開。stop()や、asynctest()などで中断してるテストを再開する時に

sample

$(function(){
    module('sample3');
    asyncTest("asyncTest",function(){
        stop(100); //100ms経っても$.getの応答がなかったら中断
        var r = $.get('/');
        r.done(function(e){
            start();
            console.log;
            ok(true,'success');
        });
        ok(true,'finish'); //中断しても実行される
    });
});

  • expect() … テスト総数。テストが期待した回数実行されたかどうか

引数に実行回数を取る。多くても少なくてもダメ。expectedオプションというのもある

sample

$(function(){
    module('sample4');
    test("even_test",function(){
        //偶数を取ってきたい
        $.each([1,2,3,4,5,6], function(i){
            var surplus = i%3; //間違えた!
            if(surplus === 0){
                ok(true, 'This number is even!');
            }
        });
        expect(3); //テストの実行は2回しかされないので、不通過になる
    });
});

上記サンプルの実行結果はこんな感じ

forked: QUnit sample – jsdo.it – share JavaScript, HTML5 and CSS

テスト方法

基本的には期待する結果をハードコーディングする。
ex) テキストベタ書き。htmlベタ書き

domのテストの場合は、テストページに期待するhtmlを書いたり、外部ファイル化して調べたり。

forked: QUnit sample – jsdo.it – share JavaScript, HTML5 and CSS

その他

ユーザの動きが必要な場合は、QUnitよりも”Slenium
ex) ログイン処理、購入確認など

Selenium – Web Browser Automation
ブラウザを選ばずWebテストを自動化するSelenium (2/3) – @IT

@kyo_ago さんが”JsTestDriver“がいいって言ってた。でもJava
JSの単体テストにJsTestDriverがおすすめ|0-9

おまけ

Wi-Fiを経由すればFiddlerできる(Charlesも一応)

Charlesは$50/1-4licenseだから買ってもいいかもね(円高だし)

日本語コメントの後ろには半角スペース入れたほうがいいよ(文字化け防止)

次回

1ヶ月に1回ぐらいできればいいね

次やるときは事前にテーマ決めるとか

参考

今から3分で qUnit の使い方を身に付ける (JavaScriptの単体テスト) – 主に言語とシステム開発に関して
QUnitの基本的な使い方 – Block Rockin’ Codes
JavaScriptで単体テストをするならQUnitはいかが? at HouseTect, JavaScriptな情報をあなたに
無職のプログラミング  QUnit & jQuery でイベント起動処理のテストを行う

【Android】orientationchangeの挙動制御

ちょこちょこまとめてみた。

まず

Androidには、window.orientation 及び window.onorientationchange を持ってるのと持ってないのがあるらしいので、

$(window).data('orientation', (function(){
  if(typeof window.orientation === 'number'){
    return Math.abs(window.orientation);
  }else{
    return (window.innerWidth < window.innerHeight) ? 0 : 90;
  }
})());
こんな感じで orientation を window の data として保持しておく。 同じように onorientationchange の方もラッパーを作っておく。
orientationChange = function(handler){
  $window = $(window);
  var has_orientation = typeof window.orientation === 'number';
  var EVENT_TYPE = has_orientation ? 'orientationchange' : 'resize';</p>

<p>({
    check : function(){
      if($window.data('orient_width') == window.innerWidth){
        return false;
      }else{
        $window.data({
          orientation : this.getOrientation(),
          orient_width : window.innerWidth,
          orient_height : window.innerHeight
        });
        return true;
      }
    },
    getOrientation : function(){
      if(has_orientation){
        return Math.abs(window.orientation);
      }else{
        return (window.innerWidth &lt; window.innerHeight) ? 0 : 90;
      }
    },
    exec : function(){
      var _self = this;</p>

<pre><code>  $window.data({
    orientation : _self.getOrientation(),
    orient_width : window.innerWidth,
    orient_height : window.innerHeight
  });

  $window.bind(EVENT_TYPE, function(e){
    $window.data('orientation', _self.getOrientation());

    if(v.UA.isAndroid &amp;amp;&amp;amp; !has_orientation){
      (function(){
        if(!_self.check()){ setTimeout(arguments.callee, 100); }
        else{
          handler();
        }
      })();
    }else{
      $window.data({
        orient_width : window.innerWidth,
        orient_height : window.innerHeight
      });
      handler();
    }
  });
  handler();
}
</code></pre>

<p>}).exec();
}
らしい、っていうのは実機で遭遇したわけではないため。

Android だった場合全部 resize イベントに回そうかとも考えたけど、 やっぱり orientation を持ってるものはそっちで処理するようにした。

上記の状態で、イベントハンドラに要素をセンタリングする処理を渡したところ、 画面の向きを変えてもハンドラが実行されていないようだったので調べてみた。

ハンドラの中で、window.innerWidth や window.innerHeight を使ってる場合、 orientation の切り替え直後は、

landscape時:portrait時の window.innerWidth/Height portrait時:landcaape時の window.innerWidth/Height

が返ってきてた。 setTimeout 使って監視してみると、100msほど遅れて切り替わる感じ。

別に動的に必要なものでもないので、読み込み時に保持してしまおう。

毎回 setTimeout 使うのもめんどくさいので。 というわけで、

device = {
  innerWidth : (function(){
    var portrait,landscape;
    var innerHeight = (isIOS) ? screen.height : window.outerHeight/window.devicePixelRatio|0;
    if($(window).data('orientation') === 0){
      portrait = window.innerWidth;
      landscape = innerHeight;
    }else{
      portrait = innerHeight;
      landscape = window.innerWidth;
    }</p>

<pre><code>return {
  portrait : portrait,
  landscape : landscape
};
</code></pre>

<p>})(),
  innerHeight : (function(){
    var portrait,landscape;
    var bar = (function(){
      if(isIOS){
        return ($(window).data('orientation')===0) ? 52 : 64;
      }else{
        return (window.outerHeight/window.devicePixelRatio) - window.innerHeight;
      }
    })();
    if($(window).data('orientation')===0){
      portrait = window.innerHeight;
      landscape = window.innerWidth - bar;
    }else{
      portrait = window.innerWidth - bar;
      landscape = window.innerHeight;
    }
    return {
      portrait : portrait,
      landscape : landscape
    };
  })()
}
window.outerHeight から取ろうとするとなぜかデバイスのY軸方向のピクセル数を返しやがりますので、 window.outerHeight / window.devicePixelRatio と、pixelRatio の倍率を考慮してやると、欲しい値になる。

outerHeight から innerHeight を引くとアドレスバーの高さが取れるので、 JSが読み込まれた時点での端末の向きを見て、window.innerWidth/Heightから、 縦/横向きの時の innerWidth/Height をそれぞれ計算して変数に入れておく。

iPhoneの場合は、ツールバーが portrait時:44px landscape時:32px なので、これにステータスバーの20pxを足した固定値でOK。 outerHeight も window.outerHeight もしくは screen.height でOK。 (そもそもこの処理も必要ないけども)

これで orientationchange の時にこっちの意図通り変化してくれない window.innerWidth/Height を見に行かなくても、 要素のセンタリングができるようになった。

何か忘れてる

持たざる者は?

window.onorientationchange を持っていない場合、 window.onresize で、window.innerWidth と window.innerHeight を比べて端末の向きを判別するので、 おとなしく状態監視をしたほうが無難な気がする。 $(window) に data として持たせると、複数要素に指定したときにうまく動かなさそうなので、 各要素ごとに持つとかそんな感じ。

【iPhone】iOS5で追加になったもろもろ

ここ見ながらまとめてる http://jsdo.it/GeckoTang/8ldM

  • position: fixed;の実装
  • overflow: scroll/auto;に対応が1本指でスクロールするようになった
    • -webkit-overflow-scrolling: touch;を指定するとスクロールバーを出せる(ネイティブなスクロールになる)
    • バーが出てた方が動きが滑らか

position:fixed;な要素と-webkit-overflow-scrollinを指定している要素が重なった時、z-indexを指定していないとおかしな挙動をする。 z-indexが指定してあれば問題ない模様。

あとは日時に関する入力タイプ(<input type=”datetime”>など)でドラムが出るようになった。

  • date
  • month
  • time
  • datetime

などなど。 valueに値が無いと空っぽ表示→クリックすると現在時刻。

<input type=”range”>がスライダー表示になった。 -webkit-appearance: slider-vertical;で縦スライダー。 ::-webkit-slider-thumbで動かすアレのスタイル指定。

あとはプライベートブラウジングのときのこれ iOS5で追加されるPrivate BrowsingモードとWebStorageの兼ね合い ::ハブろぐ はやっぱりそうなっていた。 try~catchで回避可能。

【iPhone】JSで画像の読み込みを行うとMobileSafariがクラッシュする(ことがある)

http://stackoverflow.com/questions/2986039/ipad-iphone-browser-crashing-when-loading-images-in-javascript http://bugs.jquery.com/ticket/6944

実際経験した状況では、 1) JSで2~10枚ほどの画像を、new Image() でロードする(画像はbase64でエンコード、dataURIで読み込み) 2) 読み込んだ画像を、canvasやCSSのbackgroundとして、50%縮小して、1~2枚ずつ描画。 3) 1、2の処理を10回ほど繰り替えすと、Safariがクラッシュする場合がある。

canvasでやるよりは、CSSでやった方が頻度が多い気がする。 たぶん縮小とかしてるのも良くない原因な気がする。 その他にも音声の読み込みをしてる場合があるので、 色々メモリは食ってそうな気がする。

【iPhone】Audioを読み込むイベントがどこまで持ちまわせるか

タイトルがイミフだけどとりあえずメモ。

iPhoneでAudioを読み込み(再生)させるには、 ユーザーによる任意のイベントが必要。

で、この「任意のイベント」がどこまで持ちまわせるか。 どこまでも持っていけるものではないみたい。

var audio = new Audio('hoge.mp3');
$('a.hoge').click(function(){
  audio.play();
});
これは大丈夫。

getJSON のコールバックでplay()

$('a.hoge').click(function(){
  $.getJSON('getjson.php', function(json){
    var audio = new Audio(json.audio_name);
    audio.play();
  });
});
これはたぶんダメ。

getJSONしてきたものをコールバックの外の変数に入れて、setTimeoutでJSONがセットされるのを確認してからplay()

$('a.hoge').click(function(){
  var json;
  $.getJSON('getjson.php', function(data){
     json = data;
  });</p>

<p>setTimeout(function(){
    if(typeof json === 'undefined') setTimeout(arguments.callee, 100);
    else{
      var audio = new Audio(json.audio_name);
      audio.play();
    }
  });
});
これもたぶんダメ。

同期通信でJSON取りに行って、play()

$('a.hoge').click(function(){
  var jsonText = $.ajax({url:'getjson.php',async:false});
  var json = JSON.parse(jsonText.responseText);</p>

<p>var audio = new Audio(json.audio_name);
  audio.play();
});
これもたぶんダメ。

試しに無名関数で囲ってみる

$('a.hoge').click(function(){
  var audio;
  (function(){
    audio = new Audio('hoge.mp3');
    audio.play();
  })();</p>

<p>});
ダメっぽい。

「任意のイベント」(ここではclick)のイベントハンドラのスコープの中で、 且つそのイベント発生後、通信が起こっていない状況じゃないと、 load/play()は出来ないような、そんな気がする。

詳細は時間があるときに検証する。

【iPhone,Android】スマートフォンでの音声再生まとめ

スマートフォン向けのWebアプリを作る際に調べたこととかそんな感じのことの自分用メモ。 音声再生周りのことに関して、でもAudioのことだけとは限らない。

Continue reading

【iPhone,Android】タッチデバイスかどうか判定する

  isTouch : (function(){
    // タッチデバイス判定
    return (document.ontouchstart !== undefined);
  })()

っていうのをオブジェクトの中に入れていつでも呼び出せるようにしてみたり。

【JS】AndroidのAudio周りのメモ

■追記 2011/6/21 17:52 ————————————————————————— 色々調べてみた結果、どうもAndroidの場合、Audioの対応状況は、 OSのバージョン如何の問題ではなく、端末によって対応状況がまちまちな模様。。

Android端末のHTML5対応状況について調べてみた | Septeni Engineers’ Blog 上記のサイトで検証してる端末が、デフォルトで搭載されてるバージョンで検証したのか、 アップデートして検証したのかがわからないので、憶測の域を過ぎないまでも、 端末によるばらつきは事実と見ていいような気がする。 (OSのバージョンとブラウザのバージョンが紐付いてるわけではない?)

とりあえずAndroidで音声再生をしたい場合は諦めてFlashにしてしまうのが良さそう(仕事でやるなら)

今度はAndroid編。 (iPhone編はこっち)

とりあえずAudio周りに限らずそこに至るまでに調べたこととか。

Continue reading

【JS】iPhoneのAudio周りのメモ

自分用にメモ iOS4.0 と 4.2 の端末で色々検証

Continue reading

【JS】iOS4.1以下で、audioを制御できない【とみせかけて犯人はPHP】

■追記 2011/6/8 19:06 —————————————————————————- 解決しました。

実際の環境では、PHPでドキュメントルートの外からmp3のデータを引っ張ってきていたのですが、 そのコード上で、

header("Content-Type: audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3");
と、Content-Type を複数指定していました。 ここを、 “audio/mpeg” のみに(こっそり)直したところ、無事iOS4.0の端末でも再生されるようになりました。 まさかPHPの方が原因だったとは・・・。

結論

iOS4.0(恐らくiOS4.1以下)では、複数の Content-Type には非対応、iOS4.2から(何故か)対応するようになった。

おまけ

ちなみに、 複数指定されていた Content-Type を、それぞれ一つずつ選んで渡してみたところ、次のような結果に。

iOS4.2 iOS4.0
audio/mpeg
audio/x-mpeg
audio/x-mpeg-3 ×
audio/mpeg3

とはいっても、例えば、 “audio/mpeg, audio/mpeg3″ のように指定しても、やっぱり鳴らなかったので、

複数指定はやっぱりダメなようです。

まだ検証中。 とりあえず検証してる内容をメモ。

何が起こったか

iOS4.1以下の端末(iPhone 3GS,iPhone4問わず)で、以下のスクリプトで音声再生ができない。 (実際に検証したのはiOS4.0の端末と、iOS4.2の端末)

var audio = new Audio('hoge.mp3');
$('#play').bind('touchend', function(){
    audio.play();
});

じゃぁなんでiOS4.1以下なの

Safari – Wikipedia iOS4.2 から Safari5 が搭載されるようになってる。怪しい。

HTMLMediaElement Class Reference | Safari Developer Library iOS4.1 まで使われていた autobuffer が廃止され、iOS4.2 からは preload というプロパティに変わっている。

この辺に原因があるのではと思い、この現象はiOS4.1以下で起こるものと推測する。

Continue reading

ホーム > JavaScript

Search
Feeds
Meta

Return to page top