script.aculo.usを読み解く

第10回 unittest.js(後編)

この記事を読むのに必要な時間:およそ 12 分

0433:  benchmark: function(operation, iterations) {
0434:    var startAt = new Date();
0435:    (iterations || 1).times(operation);
0436:    var timeTaken = ((new Date())-startAt);
0437:    this.info((arguments[2] || 'Operation') + ' finished ' + 
0438:       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
0439:    return timeTaken;
0440:  },

433~440行目のbenchmarkは,処理の実行にかかる時間を計測する関数です。1番めの引数の処理を,2番めの引数の回数(デフォルトは1回)だけ繰り返して,それにかかったミリ秒数を返します。3番めの引数に処理の名前を渡すことができます。

437行目で,infoメソッドで,人間に読みやすいメッセージを追加します。

0441:  _isVisible: function(element) {
0442:    element = $(element);
0443:    if(!element.parentNode) return true;
0444:    this.assertNotNull(element);
0445:    if(element.style && Element.getStyle(element, 'display') == 'none')
0446:      return false;
0447:    
0448:    return this._isVisible(element.parentNode);
0449:  },

441~449行目の_isVisibleは,要素が見える状態かどうかを判定する関数です。assertVisibleとassertNotVisibleの内部で使われます。要素自身のCSSのdisplay属性が'none'でなくても,その親要素が'none'なら結局見えないことになりますから,それも含めてチェックします。

448行目で,再帰で親要素をたどります。

0450:  assertNotVisible: function(element) {
0451:    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
0452:  },

450~452行目のassertNotVisibleは,要素が必ず見えない状態であるというアサートです。上述の_isVisibleを使います。そのため,assertHiddenと違って,親要素の状態に左右されます。

0453:  assertVisible: function(element) {
0454:    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
0455:  },

453~455行目のassertVisibleは,assertNotVisibleの反対で,要素が必ず見える状態であるというアサートです。上述の_isVisibleを使います。そのため,親要素もすべて見える状態である必要があります。

0456:  benchmark: function(operation, iterations) {
0457:    var startAt = new Date();
0458:    (iterations || 1).times(operation);
0459:    var timeTaken = ((new Date())-startAt);
0460:    this.info((arguments[2] || 'Operation') + ' finished ' + 
0461:       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
0462:    return timeTaken;
0463:  }
0464:}
0465:

benchmarkが2回,まったく同じ内容で定義されています。ミスでしょう。

Test.Unit.Testcase

Test.Unit.Testcaseは,テストの実行についての情報を扱うクラスです。Test.Unit.Assertionsを継承します。

0466:Test.Unit.Testcase = Class.create();
0467:Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
0468:  initialize: function(name, test, setup, teardown) {
0469:    Test.Unit.Assertions.prototype.initialize.bind(this)();
0470:    this.name           = name;
0471:    
0472:    if(typeof test == 'string') {
0473:      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
0474:      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
0475:      this.test = function() {
0476:        eval('with(this){'+test+'}');
0477:      }
0478:    } else {
0479:      this.test = test || function() {};
0480:    }
0481:    
0482:    this.setup          = setup || function() {};
0483:    this.teardown       = teardown || function() {};
0484:    this.isWaiting      = false;
0485:    this.timeToWait     = 1000;
0486:  },

468~486行目のinitializeは,インスタンスの初期化をする関数です。引数にテスト名,テスト内容(関数か,その中身を記述した文字列⁠⁠,テスト前に実行する処理,テスト後に実行する処理をとります。

469行目で,継承元のTest.Unit.Assertionsのinitializeを呼びます。

470行目の,this.nameはテスト名です。

472~477行目で,テスト内容を文字列で与えた場合を扱います。

473,474行目は,BDDスタイルの特殊な記述を扱うための正規表現置換のようですが,これについては全く文書が見つからないので,解説を控えます。

475行目で,テスト内容の文字列をevalで解釈します。

482行目のsetupは,テスト前に実行する処理です。

483行目のteardownは,テスト後に実行する処理です。

484行目のisWaitingは,テストの内部でwait関数が呼ばれたときにtrueになるフラグで,テストの実行を待っている状態であることを表します。

485行目のtimeToWaitは,テストの内部でwait関数が呼ばれたときに設定される,待ち時間です。

0487:  wait: function(time, nextPart) {
0488:    this.isWaiting = true;
0489:    this.test = nextPart;
0490:    this.timeToWait = time;
0491:  },

487~491行目のwaitは,テストの実行を指定時間だけ待つ関数です。引数に時間と,続きのテスト処理をとります。この処理は,Test.Unit.RunnerクラスのrunTestsも絡むので少々複雑です。

489行目で,テスト内容をnextPartに置き換えているのがキモです。指定時間後のrunTestsで,nextPartが実行されるというわけです。

0492:  run: function() {
0493:    try {
0494:      try {
0495:        if (!this.isWaiting) this.setup.bind(this)();
0496:        this.isWaiting = false;
0497:        this.test.bind(this)();
0498:      } finally {
0499:        if(!this.isWaiting) {
0500:          this.teardown.bind(this)();
0501:        }
0502:      }
0503:    }
0504:    catch(e) { this.error(e); }
0505:  }
0506:});
0507:

492~506行目のrunは,テストを実行する関数です。isWaitingが絡んでいるので,理解しづらいと思います。まず,テストの初回のrunの呼び出し時には,isWaitingは必ずfalseです。そこでsetupが呼ばれます。次にtestが呼ばれます。testの内部でwait関数を使っていなければ,isWaitingはそのままfalseで進むので,teardownが呼ばれて,終了です。testの内部でwait関数を使うと,isWaitingがtrueになります。teardownを呼ばずにこのrun関数を抜けて,Test.Unit.RunnerのrunTests関数に戻ります。そこで再び,wait関数で渡したnextPartが指定時間後にrunで呼ばれる仕組みになっています。

495行目で,isWaitingを見ることで,初回のrunの呼び出しかわかります。その場合はsetupを呼びます。

497行目で,テストを実行します。test関数をthisにバインドしてから呼びます。

498行目で,テストの終了後の処理をします。isWaitingを見ることで,内部でwait関数を呼ばなかったことがわかります。その場合はteardownを呼びます。

504行目で,テストの実行中にエラーがあった場合は,errorを呼びます。

著者プロフィール

源馬照明(げんまてるあき)

名古屋大学大学院多元数理科学研究科1年。学部生のときにSchemeの素晴らしさを知ったのをきっかけに,関数型言語の世界へ。JavaScriptに,ブラウザからすぐに試せる関数型言語としての魅力と将来性を感じている。

ブログ:Gemmaの日記