script.aculo.usを読み解く

第10回unittest.js(後編)

前回に引き続いて、unittest.jsを解説します。豊富に用意されているアサートの関数名は、JUnitを使った経験があるかたには、なじみのあるものだと思います。

Test.Unit.Assertions

Test.Unit.Assertionsは、テスト内部で使うアサートのクラスです。アサートの成功、失敗、エラーの数もこのクラスで管理します。

0251:Test.Unit.Assertions = Class.create();
0252:Test.Unit.Assertions.prototype = {
0253:  initialize: function() {
0254:    this.assertions = 0;
0255:    this.failures   = 0;
0256:    this.errors     = 0;
0257:    this.messages   = [];
0258:  },

252~258行目のinitializeは、インスタンスの初期化をする関数です。assertionsはアサートの成功の数、failuresは失敗の数、errorsはエラーの数、messagesは、テストのメッセージをまとめるための配列です。

0259:  summary: function() {
0260:    return (
0261:      this.assertions + " assertions, " + 
0262:      this.failures   + " failures, " +
0263:      this.errors     + " errors" + "\n" +
0264:      this.messages.join("\n"));
0265:  },

259~265行目のsummaryは、アサートの成功の数、失敗の数、エラーの数、メッセージをまとめた文字列を返す関数です。

0266:  pass: function() {
0267:    this.assertions++;
0268:  },

266~268行目のpassは、アサートが成功したときに呼ばれる関数です。assertionsをインクリメントします。

0269:  fail: function(message) {
0270:    this.failures++;
0271:    this.messages.push("Failure: " + message);
0272:  },

269~272行目のfailは、アサートが失敗したときに呼ばれる関数です。failuresをインクリメントします。失敗のメッセージを追加します。

0273:  info: function(message) {
0274:    this.messages.push("Info: " + message);
0275:  },

273~275行目のinfoは、メッセージを追加する関数です。

0276:  error: function(error) {
0277:    this.errors++;
0278:    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
0279:  },

276~279行目のerrorは、アサートの実行中にエラーがあったときに呼ばれる関数です。errorsをインクリメントします。エラーメッセージを追加します。

0280:  status: function() {
0281:    if (this.failures > 0) return 'failed';
0282:    if (this.errors > 0) return 'error';
0283:    return 'passed';
0284:  },

280~284行目のstatusは、アサートの結果から、失敗がひとつでもあれば'failed'を、同様にエラーがあれば'error'を、何もなければ'passed'を返す関数です。

アサートのための関数群

0285:  assert: function(expression) {
0286:    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
0287:    try { expression ? this.pass() : 
0288:      this.fail(message); }
0289:    catch(e) { this.error(e); }
0290:  },

285~290行目のassertは、与える式が必ずtrueを返すというアサートです。

0291:  assertEqual: function(expected, actual) {
0292:    var message = arguments[2] || "assertEqual";
0293:    try { (expected == actual) ? this.pass() :
0294:      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
0295:        '", actual "' + Test.Unit.inspect(actual) + '"'); }
0296:    catch(e) { this.error(e); }
0297:  },

291~297行目のassertEqualは、"予想の値"、"実際の値"として、それらが必ず等しいというアサートです。

0298:  assertInspect: function(expected, actual) {
0299:    var message = arguments[2] || "assertInspect";
0300:    try { (expected == actual.inspect()) ? this.pass() :
0301:      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
0302:        '", actual "' + Test.Unit.inspect(actual) + '"'); }
0303:    catch(e) { this.error(e); }
0304:  },

298~304行目のassertInspectは、"予想のInspect"、"実際の値"として、そのインスペクトが必ず等しいというアサートです。

0305:  assertEnumEqual: function(expected, actual) {
0306:    var message = arguments[2] || "assertEnumEqual";
0307:    try { $A(expected).length == $A(actual).length && 
0308:      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
0309:        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
0310:          ', actual ' + Test.Unit.inspect(actual)); }
0311:    catch(e) { this.error(e); }
0312:  },

305~312行目のassertEnumEqualは、"予想の配列"、"実際の配列"として、それらの配列の内容が必ず等しいというアサートです。

0313:  assertNotEqual: function(expected, actual) {
0314:    var message = arguments[2] || "assertNotEqual";
0315:    try { (expected != actual) ? this.pass() : 
0316:      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
0317:    catch(e) { this.error(e); }
0318:  },

313~318行目のassertNotEqualは、assertEqualの反対で、"予想の値"、"実際の値"として、それらが必ず等しくないというアサートです。

0319:  assertIdentical: function(expected, actual) { 
0320:    var message = arguments[2] || "assertIdentical"; 
0321:    try { (expected === actual) ? this.pass() : 
0322:      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
0323:        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
0324:    catch(e) { this.error(e); } 
0325:  },

319~325行目のassertIdenticalは、assertEqualとほとんど同じですが、比較に===を使うところが違います。"予想の値"、"実際の値"として、それらが===で比較して必ず等しいというアサートです。

0326:  assertNotIdentical: function(expected, actual) { 
0327:    var message = arguments[2] || "assertNotIdentical"; 
0328:    try { !(expected === actual) ? this.pass() : 
0329:      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
0330:        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
0331:    catch(e) { this.error(e); } 
0332:  },

326~332行目のassertNotIdenticalは、assertIdenticalの反対で、"予想の値"、"実際の値"として、それらが===で比較して必ず等しくないというアサートです。

0333:  assertNull: function(obj) {
0334:    var message = arguments[1] || 'assertNull'
0335:    try { (obj==null) ? this.pass() : 
0336:      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
0337:    catch(e) { this.error(e); }
0338:  },

333~338行目のassertNullは、オブジェクトが必ずnullであるというアサートです。

0339:  assertMatch: function(expected, actual) {
0340:    var message = arguments[2] || 'assertMatch';
0341:    var regex = new RegExp(expected);
0342:    try { (regex.exec(actual)) ? this.pass() :
0343:      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
0344:    catch(e) { this.error(e); }
0345:  },

339~345行目のassertMatchは、"正規表現の文字列"、"実際の文字列"として、その正規表現が必ずマッチするというアサートです。

0346:  assertHidden: function(element) {
0347:    var message = arguments[1] || 'assertHidden';
0348:    this.assertEqual("none", element.style.display, message);
0349:  },

346~349行目のassertHiddenは、要素が必ず非表示であるというアサートです。要素のCSSのdisplay属性が必ず"none"と等しいとします。

0350:  assertNotNull: function(object) {
0351:    var message = arguments[1] || 'assertNotNull';
0352:    this.assert(object != null, message);
0353:  },

350~353行目のassertNotNullは、assertNullの反対で、オブジェクトが必ずnullでないというアサートです。

0354:  assertType: function(expected, actual) {
0355:    var message = arguments[2] || 'assertType';
0356:    try { 
0357:      (actual.constructor == expected) ? this.pass() : 
0358:      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
0359:        '", actual "' + (actual.constructor) + '"'); }
0360:    catch(e) { this.error(e); }
0361:  },

354~361行目のassertTypeは、"予想のコンストラクタ"、"実際の値"として、必ずそのコンストラクタの値であるというアサートですCore JavaScript 1.5 Reference:Object:constructor⁠。

0362:  assertNotOfType: function(expected, actual) {
0363:    var message = arguments[2] || 'assertNotOfType';
0364:    try { 
0365:      (actual.constructor != expected) ? this.pass() : 
0366:      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
0367:        '", actual "' + (actual.constructor) + '"'); }
0368:    catch(e) { this.error(e); }
0369:  },

362~369行目のassertNotOfTypeは、assertTypeの反対で、"予想のコンストラクタ"、"実際の値"として、必ずそのコンストラクタの値でないというアサートです。

0370:  assertInstanceOf: function(expected, actual) {
0371:    var message = arguments[2] || 'assertInstanceOf';
0372:    try { 
0373:      (actual instanceof expected) ? this.pass() : 
0374:      this.fail(message + ": object was not an instance of the expected type"); }
0375:    catch(e) { this.error(e); } 
0376:  },

370~376行目のassertInstanceOfは、"予想の型"、"実際の値"として、必ずその型の値であるというアサートです。

0377:  assertNotInstanceOf: function(expected, actual) {
0378:    var message = arguments[2] || 'assertNotInstanceOf';
0379:    try { 
0380:      !(actual instanceof expected) ? this.pass() : 
0381:      this.fail(message + ": object was an instance of the not expected type"); }
0382:    catch(e) { this.error(e); } 
0383:  },

377~383行目のassertNotInstanceOfは、assertInstanceOfの反対で、"予想の型"、"実際の値"として、必ずその型の値でないというアサートです。

0384:  assertRespondsTo: function(method, obj) {
0385:    var message = arguments[2] || 'assertRespondsTo';
0386:    try {
0387:      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
0388:      this.fail(message + ": object doesn't respond to [" + method + "]"); }
0389:    catch(e) { this.error(e); }
0390:  },

384~390行目のassertRespondsToは、"メソッド名"、"オブジェクト"として、そのメソッドが必ずあるというアサートです。

0391:  assertReturnsTrue: function(method, obj) {
0392:    var message = arguments[2] || 'assertReturnsTrue';
0393:    try {
0394:      var m = obj[method];
0395:      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
0396:      m() ? this.pass() : 
0397:      this.fail(message + ": method returned false"); }
0398:    catch(e) { this.error(e); }
0399:  },

391~399行目のassertReturnsTrueは、"メソッド名"、"オブジェクト"として、そのメソッドを呼ぶと必ずtrueを返すというアサートです。

0400:  assertReturnsFalse: function(method, obj) {
0401:    var message = arguments[2] || 'assertReturnsFalse';
0402:    try {
0403:      var m = obj[method];
0404:      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
0405:      !m() ? this.pass() : 
0406:      this.fail(message + ": method returned true"); }
0407:    catch(e) { this.error(e); }
0408:  },

400~408行目のassertReturnsFalseは、assertReturnsTrueの反対で、"メソッド名"、"オブジェクト"として、そのメソッドを呼ぶと必ずfalseを返すというアサートです。

0409:  assertRaise: function(exceptionName, method) {
0410:    var message = arguments[2] || 'assertRaise';
0411:    try { 
0412:      method();
0413:      this.fail(message + ": exception expected but none was raised"); }
0414:    catch(e) {
0415:      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
0416:    }
0417:  },

409~417行目のassertRaiseは、"例外の名前"(nullでも構いません⁠⁠、"関数"として、その関数を呼ぶと必ずその名前の例外が発生するというアサートです。nullを指定したときは、とにかく例外が発生すれば成立します。

0418:  assertElementsMatch: function() {
0419:    var expressions = $A(arguments), elements = $A(expressions.shift());
0420:    if (elements.length != expressions.length) {
0421:      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
0422:      return false;
0423:    }
0424:    elements.zip(expressions).all(function(pair, index) {
0425:      var element = $(pair.first()), expression = pair.last();
0426:      if (element.match(expression)) return true;
0427:      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
0428:    }.bind(this)) && this.pass();
0429:  },

418~429行目のassertElementsMatchは、1番めの引数に要素の配列を、残りの引数に、その配列の長さと同じだけのCSSセレクタの文字列をとって、それぞれの要素がそれぞれのCSSセレクタにElement.matchで必ずマッチするというアサートです。

0430:  assertElementMatches: function(element, expression) {
0431:    this.assertElementsMatch([element], expression);
0432:  },

430~432行目のassertElementMatchesは、assertElementsMatchの単数形です。引数に要素とCSSセレクタの文字列をとって、その要素がそのCSSセレクタにElement.matchで必ずマッチするというアサートです。

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を呼びます。

BDDスタイルのテスト記述

BDDはビヘイビア駆動開発の略で、このライブラリでは以下のように記述します。JavaScript自体の制限から、RubyのRSpecほどエレガントでないところもありますが、これまでより読みやすいのが特徴です。こちらにRSpecの解説記事があります。

0001:Test.context("BDD-style testing",{
0002:  
0003:  setup: function() {
0004:  },
0005:  
0006:  teardown: function() {
0007:  },
0008:  
0009:  'should automatically add extensions to strings': function(){
0010:    'a'.shouldEqual('a');
0011:    'a'.shouldNotEqual('b');
0012:    'a'.shouldNotBeNull();
0013:    'a'.shouldBeA(String);
0014:    
0015:    var aString = 'boo!';
0016:    aString.shouldEqual('boo!');
0017:    aString.shouldBeA(String);
0018:    aString.shouldNotBeA(Number);
0019:  },
0020:})

それではコードに戻りましょう。

0508:// *EXPERIMENTAL* BDD-style testing to please non-technical folk
0509:// This draws many ideas from RSpec http://rspec.rubyforge.org/
0510:
0511:Test.setupBDDExtensionMethods = function(){
0512:  var METHODMAP = {
0513:    shouldEqual:     'assertEqual',
0514:    shouldNotEqual:  'assertNotEqual',
0515:    shouldEqualEnum: 'assertEnumEqual',
0516:    shouldBeA:       'assertType',
0517:    shouldNotBeA:    'assertNotOfType',
0518:    shouldBeAn:      'assertType',
0519:    shouldNotBeAn:   'assertNotOfType',
0520:    shouldBeNull:    'assertNull',
0521:    shouldNotBeNull: 'assertNotNull',
0522:    
0523:    shouldBe:        'assertReturnsTrue',
0524:    shouldNotBe:     'assertReturnsFalse',
0525:    shouldRespondTo: 'assertRespondsTo'
0526:  };
0527:  var makeAssertion = function(assertion, args, object) { 
0528:   	this[assertion].apply(this,(args || []).concat([object]));
0529:  }
0530:  
0531:  Test.BDDMethods = {};   
0532:  $H(METHODMAP).each(function(pair) { 
0533:    Test.BDDMethods[pair.key] = function() { 
0534:       var args = $A(arguments); 
0535:       var scope = args.shift(); 
0536:       makeAssertion.apply(scope, [pair.value, args, this]); }; 
0537:  });
0538:  
0539:  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
0540:    function(p){ Object.extend(p, Test.BDDMethods) }
0541:  );
0542:}
0543:

508~543行目のTest.setupBDDExtensionMethodsは、BDDスタイルのテスト記述を実現するための関数です。RSpecから多くの着想を得ています。

512~526行目は、BDDスタイルのメソッド名と、Test.Unit.Assertionのメソッド名の対応付けです。

527行目のmakeAssertionは、assertionにTest.Unit.Assertionのメソッド名を、argsにBDDスタイルのメソッドの引数、objectにメソッドを呼んだオブジェクトを取って、実際のTest.Unit.Assertionの関数をapplyで呼ぶ関数です。

531行目で、BDDのメソッドを全て挙げるためのハッシュテーブルを用意します。

532~537行目で、BDDスタイルのメソッドの中身を作ります。実体は、BDDスタイルのメソッドの呼び出し元のオブジェクトと、その引数の順序を組み換えて、Test.Unit.Assertionの関数を呼ぶようになっています。

539~541行目で、BDDスタイルになるように、配列、文字列、数字、ブール値のそれぞれにメソッドを追加します。

0544:Test.context = function(name, spec, log){
0545:  Test.setupBDDExtensionMethods();
0546:  
0547:  var compiledSpec = {};
0548:  var titles = {};
0549:  for(specName in spec) {
0550:    switch(specName){
0551:      case "setup":
0552:      case "teardown":
0553:        compiledSpec[specName] = spec[specName];
0554:        break;
0555:      default:
0556:        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
0557:        var body = spec[specName].toString().split('\n').slice(1);
0558:        if(/^\{/.test(body[0])) body = body.slice(1);
0559:        body.pop();
0560:        body = body.map(function(statement){ 
0561:          return statement.strip()
0562:        });
0563:        compiledSpec[testName] = body.join('\n');
0564:        titles[testName] = specName;
0565:    }
0566:  }
0567:  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
0568:};

544~568行目のTest.contextは、これまでTest.Unit.Runnerで実行していたテストを、BDDスタイルに書くための関数です。引数のnameにテスト全体の名前を、specにテストの記述を、logにログを出力する要素のDOM idを取ります。

545行目で、前述のsetupBDDExtensionMethodsを呼んで準備をします。

551、552行目で、setup、teardownの処理をspecから取りだします。

555行目以降が本質的な部分です。

556行目で、テスト名をBDDスタイルからJavaScript風に変換します。具体的には、'should automatically add extensions to strings'が'testShouldAutomaticallyAddExtensionsToStrings'になります。

557行目の、bodyとは、関数の中身のことを指します。関数をtoStringで文字列にして、splitで文字列の配列にしてから、このような処理をして関数の中身を取り出します。

はじめの状態です。

function()
{
  'a'.shouldEqual('a');
  'a'.shouldNotEqual('b');
  'a'.shouldNotBeNull();
  'a'.shouldBeA(String);
}

557行目のslice(1)で、最初の'function()'を取り除きます。

{
  'a'.shouldEqual('a');
  'a'.shouldNotEqual('b');
  'a'.shouldNotBeNull();
  'a'.shouldBeA(String);
}

558行目のslice(1)で、最初の'{'を取り除きます。

この処理は'function(){'の書法ならば必要ないので、正規表現でチェックしています。

  'a'.shouldEqual('a');
  'a'.shouldNotEqual('b');
  'a'.shouldNotBeNull();
  'a'.shouldBeA(String);
}

559行目のpop()で、最後の'}'を取り除きます。

  'a'.shouldEqual('a');
  'a'.shouldNotEqual('b');
  'a'.shouldNotBeNull();
  'a'.shouldBeA(String);

560行目のstatement.strip()で、全体から空白を取り除きます。

'a'.shouldEqual('a');
'a'.shouldNotEqual('b');
'a'.shouldNotBeNull();
'a'.shouldBeA(String);

563行目で、こうして得られた結果の、文字列の配列を結合し、文字列にして完了です。後で、Test.Unit.Runnerの内部でevalを呼んで、関数に解釈します。

564行目で、テストのタイトルを設定します。

567行目で、これらをTest.Unit.Runnerに渡します。

おすすめ記事

記事・ニュース一覧