Skip to content

Instantly share code, notes, and snippets.

@teppeis
Last active May 18, 2017 02:35
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teppeis/54cc85137e6b581a6f95 to your computer and use it in GitHub Desktop.
Save teppeis/54cc85137e6b581a6f95 to your computer and use it in GitHub Desktop.
Promise/非同期のテストを簡単にする新しいアサーションヘルパーのご紹介

Promise/非同期のテストを簡単にする新しいアサーションヘルパーのご紹介

この前のブログ記事見た人はごめん。

Promise/非同期のテストは難しい

どこが間違ってるか分かります?

// mayBeRejected()がrejectしたPromiseのエラーを確かめるテスト
it("mayBeRejected()が'woo'でrejectする", function() {
    return mayBeRejected().catch(function(error) {
        assert(error.message === "woo");
    });
});

Promise本を読もう

Chapter.3 Promiseのテストに詳しく書いてあります。 難しいです。

Fake timer

sinon (rolex) とかでsetTimeoutとかは無理やり直列化することもできるけど

it("500ms後にアニメーションが終わる", function() {
  var clock = sinon.useFakeTimers();
  var el = $("#target");
  el.animate({ height: "200px", width: "200px" });
  assertEquals("100px", el.css("height"));
  assertEquals("100px", el.css("width"));

  clock.tick(500);
  assertEquals("200px", el.css("height"));
  assertEquals("200px", el.css("width"));
});

PromiseはFake timerできない

多分無理。やりかた知ってる人いたら教えて。

一方そのころ

assertの回数を数える宗派がいた: tape

var test = require('tape');
test('timing test', function (t) {
    t.plan(2);

    t.equal(typeof Date.now, 'function');
    var start = Date.now();

    setTimeout(function () {
        t.equal(Date.now() - start, 100);
    }, 100);
});

最近のQUnit

こちらも数える宗派。

QUnit.test("mayBeRejected()が'woo'でrejectする", function(assert) {
    assert.expect(2);
    return mayBeRejected().catch(function(error) {
        assert.ok(error instanceof Error);
        assert.ok(error.message === "woo");
    });
});

mochaにできない理由

mochaはテストランナーとアサーションライブラリが分離されてる。 自由に組み合わせられる利点はあるが、tapeやQUnitのように密連携できないので、アサートの回数を数えられない。

http://www.slideshare.net/teppeis/javascript-testwhywhathow/33

ならばAST黒魔術を使うまでだ!

作った: https://github.com/teppeis/esplan

やること

  1. アサート回数をカウントできるようにassertパッケージをラップ
  2. esprimaでAST化したテストコードを走査してit('title', fn)を抽出
  3. テスト関数内部を走査して計画されているアサートの回数をカウント
  4. テスト関数の先頭にアサート回数をassert.$$plan()として挿入
  5. テストを実行し、計画されたアサート回数に達するかタイムアウトまで待つ

変換前

var esplan = require('esplan');
var assert = esplan.register(require('assert'));

describe('Promise', function() {
    it('can not detect an assertion error in `then` function', function() {
        mayBeResolve().then(function(value) {
            assert.equal(value.length, 2);
            assert.equal(value[0], 'foo');
            assert.equal(value[1], 'bar');
        });
    });
});

変換後!

var esplan = require('esplan');
var assert = esplan.register(require('assert'));

describe('Promise', function () {
    // `$$done`が引数に追加された
    it('can not detect an assertion error in `then` function', function($$done) {
        assert.$$plan(this, 3, $$done); // 3回のassertを計画
        mayBeResolve().then(function (value) {
            assert.equal(value.length, 2);
            assert.equal(value[0], 'foo');
            assert.equal(value[1], 'bar');
        });
    });
});

エラーメッセージ

"Timeout!"じゃないよ!

Error: Expected 3 assertions, but actually 2 assertions called

アサート回数は人間が数えるべきではない

jQueryのテストでアサート109回とかある。

自動化しよ。

いまここ

まだPoCなので

  • power-assertとの同居(たぶん順番に気をつければできそう)
  • よりeasyに(各種環境やビルドツールへの対応)
  • source map対応
  • 標準assertとmocha BDDスタイル以外への対応
  • 適用対象外にする構文
  • コンパイルしなくても使える構文

ご意見ください

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment