Ruby on RailsがリポジトリをGitHubに移したのにあわせて、
今回解説するunittest.
Script.
それではコードを見ていきましょう。
0001:// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
0002:
0003:// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
0004:// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
0005:// (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
0006://
0007:// script.aculo.us is freely distributable under the terms of an MIT-style license.
0008:// For details, see the script.aculo.us web site: http://script.aculo.us/
0009:
1~9行めは著作権表示です。
Event.simulateMouse
0010:// experimental, Firefox-only
0011:Event.simulateMouse = function(element, eventName) {
0012: var options = Object.extend({
0013: pointerX: 0,
0014: pointerY: 0,
0015: buttons: 0,
0016: ctrlKey: false,
0017: altKey: false,
0018: shiftKey: false,
0019: metaKey: false
0020: }, arguments[2] || {});
0021: var oEvent = document.createEvent("MouseEvents");
0022: oEvent.initMouseEvent(eventName, true, true, document.defaultView,
0023: options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
0024: options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
0025:
0026: if(this.mark) Element.remove(this.mark);
0027: this.mark = document.createElement('div');
0028: this.mark.appendChild(document.createTextNode(" "));
0029: document.body.appendChild(this.mark);
0030: this.mark.style.position = 'absolute';
0031: this.mark.style.top = options.pointerY + "px";
0032: this.mark.style.left = options.pointerX + "px";
0033: this.mark.style.width = "5px";
0034: this.mark.style.height = "5px;";
0035: this.mark.style.borderTop = "1px solid red;"
0036: this.mark.style.borderLeft = "1px solid red;"
0037:
0038: if(this.step)
0039: alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
0040:
0041: $(element).dispatchEvent(oEvent);
0042:};
0043:
10~43行目のEvent.
initMouseEventに渡す値をオプションで設定します。設定の詳細についてはGecko DOM ReferenceのinitMouseEventの項を参照してください。
- pointerX、
pointerY - マウスポインタの座標です。
- buttons
- どのボタンでクリックするかです。0なら通常の左ボタン、
1なら中ボタン、 2なら右ボタンです。 - ctrlKey、
altKey、 shiftKey、 metaKey - ctrlKeyは、
Ctrlキーが押されているかです。その他も同様です。
21行目のdocument.
22~25行目でinitMouseEventでイベントの詳細な内容を設定します。
27~37行目でdiv要素を作って、
38行目でthis.
41行目で実際にイベントを発生させます。
Event.simulateKey
0044:// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
0045:// You need to downgrade to 1.0.4 for now to get this working
0046:// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
0047:Event.simulateKey = function(element, eventName) {
0048: var options = Object.extend({
0049: ctrlKey: false,
0050: altKey: false,
0051: shiftKey: false,
0052: metaKey: false,
0053: keyCode: 0,
0054: charCode: 0
0055: }, arguments[2] || {});
0056:
0057: var oEvent = document.createEvent("KeyEvents");
0058: oEvent.initKeyEvent(eventName, true, true, window,
0059: options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
0060: options.keyCode, options.charCode );
0061: $(element).dispatchEvent(oEvent);
0062:};
0063:
44~62行目のEvent.
- ctrlKey、
altKey、 shiftKey、 metaKey - ctrlKeyは、
Ctrlキーが押されているかです。その他も同様です。 - keyCode、
charCode - キーコードです。
57行目のdocument.
58~60行目のinitKeyEventでイベントの詳細な内容を設定します。
61行目で実際にイベントを発生させます。
0064:Event.simulateKeys = function(element, command) {
0065: for(var i=0; i<command.length; i++) {
0066: Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
0067: }
0068:};
0069:
64~69行目のEvent.
Test.Unit.Logger
Test.
0070:var Test = {}
0071:Test.Unit = {};
0072:
0073:// security exception workaround
0074:Test.Unit.inspect = Object.inspect;
0075:
70、
74行目で、
0076:Test.Unit.Logger = Class.create();
0077:Test.Unit.Logger.prototype = {
0078: initialize: function(log) {
0079: this.log = $(log);
0080: if (this.log) {
0081: this._createLogTable();
0082: }
0083: },
78~83行目のinitializeは、
79行目で、
80行目で、
0084: start: function(testName) {
0085: if (!this.log) return;
0086: this.testName = testName;
0087: this.lastLogLine = document.createElement('tr');
0088: this.statusCell = document.createElement('td');
0089: this.nameCell = document.createElement('td');
0090: this.nameCell.className = "nameCell";
0091: this.nameCell.appendChild(document.createTextNode(testName));
0092: this.messageCell = document.createElement('td');
0093: this.lastLogLine.appendChild(this.statusCell);
0094: this.lastLogLine.appendChild(this.nameCell);
0095: this.lastLogLine.appendChild(this.messageCell);
0096: this.loglines.appendChild(this.lastLogLine);
0097: },
84~97行目のstartは、

0098: finish: function(status, summary) {
0099: if (!this.log) return;
0100: this.lastLogLine.className = status;
0101: this.statusCell.innerHTML = status;
0102: this.messageCell.innerHTML = this._toHTML(summary);
0103: this.addLinksToResults();
0104: },
98~104行目のfinishは、
103行目では、
0105: message: function(message) {
0106: if (!this.log) return;
0107: this.messageCell.innerHTML = this._toHTML(message);
0108: },
105~108行目のmessageは、
0109: summary: function(summary) {
0110: if (!this.log) return;
0111: this.logsummary.innerHTML = this._toHTML(summary);
0112: },
109~112行目のsummaryは、
0113: _createLogTable: function() {
0114: this.log.innerHTML =
0115: '<div id="logsummary"></div>' +
0116: '<table id="logtable">' +
0117: '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
0118: '<tbody id="loglines"></tbody>' +
0119: '</table>';
0120: this.logsummary = $('logsummary')
0121: this.loglines = $('loglines');
0122: },
113~122行目の_createLogTableは、
0123: _toHTML: function(txt) {
0124: return txt.escapeHTML().replace(/\n/g,"<br/>");
0125: },
123~125行目の_toHTMLは、
0126: addLinksToResults: function(){
0127: $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
0128: td.title = "Run only this test"
0129: Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
0130: });
0131: $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
0132: td.title = "Run all tests"
0133: Event.observe(td, 'click', function(){ window.location.search = "";});
0134: });
0135: }
0136:}
0137:
126~136行目のaddLinksToResultsは、
131~135行目で、
Test.Unit.Runner
Test.
0001:new Test.Unit.Runner({
0002: setup: function() {},
0003: teardown: function () {},
0004: testAssertEqual: function() { with(this) {
0005: assertEqual(0, 0);
0006:
0007: assertEqual(0,'0');
0008: assertEqual(65.0, 65);
0009:
0010: assertEqual("a", "a");
0011:
0012: assertNotEqual(0, 1);
0013: assertNotEqual("a","b");
0014: }}
0015:})
それではコードに戻りましょう。
0138:Test.Unit.Runner = Class.create();
0139:Test.Unit.Runner.prototype = {
0140: initialize: function(testcases) {
0141: this.options = Object.extend({
0142: testLog: 'testlog'
0143: }, arguments[1] || {});
0144: this.options.resultsURL = this.parseResultsURLQueryParameter();
0145: this.options.tests = this.parseTestsQueryParameter();
0146: if (this.options.testLog) {
0147: this.options.testLog = $(this.options.testLog) || null;
0148: }
0149: if(this.options.tests) {
0150: this.tests = [];
0151: for(var i = 0; i < this.options.tests.length; i++) {
0152: if(/^test/.test(this.options.tests[i])) {
0153: this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
0154: }
0155: }
0156: } else {
0157: if (this.options.test) {
0158: this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
0159: } else {
0160: this.tests = [];
0161: for(var testcase in testcases) {
0162: if(/^test/.test(testcase)) {
0163: this.tests.push(
0164: new Test.Unit.Testcase(
0165: this.options.context ? ' -> ' + this.options.titles[testcase] : testcase,
0166: testcases[testcase], testcases["setup"], testcases["teardown"]
0167: ));
0168: }
0169: }
0170: }
0171: }
0172: this.currentTest = 0;
0173: this.logger = new Test.Unit.Logger(this.options.testLog);
0174: setTimeout(this.runTests.bind(this), 1000);
0175: },
140~175行目のinitializeは、
以下のオプションがあります。
- testLog
- 結果を出力する要素のDOM idです。デフォルトは'testlog'です。
- resultsURL
- クエリパラメータresultsURLから与えます。結果の送信先のURLです。
- tests
- クエリパラメータtestsから与えます。引数で与えたtestcasesの中から、
実行したいテスト名をカンマ区切りで指定します。 - test
- 引数で与えたtestcasesの中から、
実行したいテスト名を1つ指定します。 - context
- 有効にすると、
次のtitlesのオプションが使われます。 - titles
- テストのそれぞれにタイトルをつけることができます。キーにテスト名、
値にタイトルのハッシュテーブルを与えます。
144行目で、
145行目で、
146行目で、
149~156行目で、
157行目で、
159~170行目で、
172行目で、
173行目で、
174行目で、
0176: parseResultsURLQueryParameter: function() {
0177: return window.location.search.parseQuery()["resultsURL"];
0178: },
176~178行目のparseResultsURLQueryParameterは、
0179: parseTestsQueryParameter: function(){
0180: if (window.location.search.parseQuery()["tests"]){
0181: return window.location.search.parseQuery()["tests"].split(',');
0182: };
0183: },
179~183行目のparseTestsQueryParameterは、
0184: // Returns:
0185: // "ERROR" if there was an error,
0186: // "FAILURE" if there was a failure, or
0187: // "SUCCESS" if there was neither
0188: getResult: function() {
0189: var hasFailure = false;
0190: for(var i=0;i<this.tests.length;i++) {
0191: if (this.tests[i].errors > 0) {
0192: return "ERROR";
0193: }
0194: if (this.tests[i].failures > 0) {
0195: hasFailure = true;
0196: }
0197: }
0198: if (hasFailure) {
0199: return "FAILURE";
0200: } else {
0201: return "SUCCESS";
0202: }
0203: },
188~203行目のgetResultは、
0204: postResults: function() {
0205: if (this.options.resultsURL) {
0206: new Ajax.Request(this.options.resultsURL,
0207: { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
0208: }
0209: },
204~209行目のpostResultsは、
0210: runTests: function() {
0211: var test = this.tests[this.currentTest];
0212: if (!test) {
0213: // finished!
0214: this.postResults();
0215: this.logger.summary(this.summary());
0216: return;
0217: }
0218: if(!test.isWaiting) {
0219: this.logger.start(test.name);
0220: }
0221: test.run();
0222: if(test.isWaiting) {
0223: this.logger.message("Waiting for " + test.timeToWait + "ms");
0224: setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
0225: } else {
0226: this.logger.finish(test.status(), test.summary());
0227: this.currentTest++;
0228: // tail recursive, hopefully the browser will skip the stackframe
0229: this.runTests();
0230: }
0231: },
210~231行目のrunTestsは、
211行目で、
212行目で、
214行目で、
215行目で、
218行目で、
221行目で、
223、
224行目で、
226行目が、
227行目で、
229行目で、
0232: summary: function() {
0233: var assertions = 0;
0234: var failures = 0;
0235: var errors = 0;
0236: var messages = [];
0237: for(var i=0;i<this.tests.length;i++) {
0238: assertions += this.tests[i].assertions;
0239: failures += this.tests[i].failures;
0240: errors += this.tests[i].errors;
0241: }
0242: return (
0243: (this.options.context ? this.options.context + ': ': '') +
0244: this.tests.length + " tests, " +
0245: assertions + " assertions, " +
0246: failures + " failures, " +
0247: errors + " errors");
0248: }
0249:}
0250:
232~250行目のsummaryは、