// compare two XML objects for lisp-style EQUAL // this takes two string var xml1 = LzDataElement.stringToLzData(str1) var xml2 = LzDataElement.stringToLzData(str2) return xmlequals(xml1, xml2); // TODO: [2002-11-09 ptw] (ActionScript condition incompatible JavaScript) // ActionScript does not obey Javascript semantics for testing whether // an expression is true in a conditional var t = typeof(condition); if (t == "string") { return condition.length > 0; } else if (t == "object") { return true; // Safe test for undefined } else if (t == "undefined") { return false; } else { return !!condition; } this.debugWrite = jsTrue(args["debugWrite"]); super.construct(parent, args); dw("DebugObject.construct(", args, ")"); this.result = null; super.construct(parent, args); dw("Test.construct(", args, ")"); if (typeof(theTestResult) == "undefined") { theTestResult = new lz.TestResult(); } this.result = theTestResult; return this.formatter.formatToString( '%s expected %#w got %#w', (jsTrue(message) ? message + ": " : ""), expected, actual); if (! semanticsTrue(condition)) { this.fail(tformat(assertion, true, condition)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (!! semanticsTrue(condition)) { this.fail(tformat(assertion, false, condition)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (! (expected == actual)) { this.fail(tformat(message, expected, actual)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (object !== null) { this.fail(tformat(message, null, object)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (object === null) { this.fail(tformat(message, "non-null value", object)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (typeof(object) != "undefined") { this.fail(tformat(message, "undefined value", object)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (typeof(object) == "undefined") { this.fail(tformat(message, "defined value", object)); } canvas.setAttribute('runTests', canvas.runTests + 1) if (expected === actual) { // In-line Test.tformat so we can invert the sense var msg = this.formatter.formatToString( '%s expected anything but %#w got %#w', message + ": ", expected, actual); this.fail(msg); } canvas.setAttribute('runTests', canvas.runTests + 1) if ($debug){ Debug.warn("You should override the TestCase.addTests method of",this, "to add your tests to the test case.", this); } if (this.testnames == null) { this.testnames = []; } this.testnames.push(testname); if (jsTrue(args["testName"])) { this.name = args.testName; delete args.testName; } super.construct(parent, args); dw("TestCase.construct(", args, ")"); super.init(); this.addTests(); if (typeof(theTestName) == "undefined") theTestName = name; dw("TestCase.runTest(", theTestName, ")"); // Invoke the test method var m = this[theTestName]; if (typeof(m) != "function") { error("method '" + theTestName + "' not found"); } else { m.call(this); } var p = cur_meth.xpathQuery('@args') var e = cur_meth.xpathQuery('@event') if (!this['del']) { this.del = (typeof(e) != "undefined" ? new LzDelegate(this, '_handler', tested_object, e) : new LzDelegate(this, '_handler')) } else if (typeof(e) != "undefined") this.del.register(tested_object, e); if (typeof(p) != "undefined") tested_object[current_method](p, del) else tested_object[current_method](del) callNext() var o = cur_meth.xpathQuery('@tester') if (typeof(o) != "undefined") this[o](res); else inspect(res); if (cur_meth.selectNext()) callNext() An extension of TestCase for testing asynchronous objects safely.

SyncTester is an extension of TestCase that is useful for testing objects whose method are to be called sequentially, in effect synchronizing methods with potentially asynchronous behavior.

To take advantage of this helper class, you must declare a dataset named "<instance name>_methods", with a root node whose children are the method names to be called synchronously. The method nodes must be named "call", and have at least the "name" attribute defined. If the method needs to be called with arguments, specify them as value of the optional "args" attribute (only one argument is currently supported).

Your specific tests will only run once a method returns. It is possible to provide an inspector method for each of the asynchronous methods declared; you reference it with the "tester" attribute of a node in the dataset. These inspector methods must be defined on the SyncTester object. If you dont specify a tester for a method, the default handler named inspect will be called with the result of the method call as an argument. You should override this method if you want to have a generic inspector for most or all of your methods.

Generally speaking, you would expect that an event is sent when a method is done. This framework allows you to specify what event indicates the end of method execution by declaring the "event" attribute. It is assumed that the sender of the event is the object referenced by the tested_object attribute, or that the following method accepts a delegate to call on completion, as the last argument. If neither of these assumptions is correct, the flow of method execution will break.

For example, if you have an instance of this class named "userinfo", then your list of methods might be declared like this:

]]>
this.failedTests = 0; this.erroredTests = 0; this.currentTest = null; this.failures = []; this.errors = []; this.messages = []; super.construct(parent, args); dw("TestResult.construct(", args, ");"); this.currentTest = test; update(); update() var f = new TestFailure(currentTest, reason); dw("TestResult.AddFailure(", f, ");"); this.failedTests++; this.failures.push(f); this.update(); var f = new TestError(currentTest, reason); dw("TestResult.AddError(", f, ");"); this.erroredTests++; errors.push(f); update(); messages.push(readout.formatToString.apply(readout, args)); update(); 0) { with (display.progress) { var bw = background.width; errorbar.setAttribute("width", bw * erroredTests / totalTests); failbar.setAttribute("width", bw * totalBad / totalTests); donebar.setAttribute("width", bw * totalTests / totalTests); } } if (totalBad > 0) { readout.setAttribute("bgcolor", lz.colors.red); } // TODO: [2002-11-10 ptw] setAttribute("text", ...) does not work? readout.setAttribute("text", lz.Browser.xmlEscape(this.toString())); ]]> Test Progress Test Results this.bringToFront(); if ($debug) { Debug.ensureVisible(); } //Debug.debug("onsuitefinish"); //this.resultstring += ("failures: "+ this.result.numFailures()+ "\n"); //this.resultstring += ("time: "+ (((new Date)['getTime']()) - this.starttime)+"\n"); this.resultstring += "finish_testsuite: "+this.testpath + " failures: "+ this.result.numFailures()+ "\n"; this.sendLogData(this.logfile, this.resultstring); if (lz.Browser.getInitArg('close_when_finished') == 'true') { Debug.info('query arg "close_when_finished" is set, closing window.'); var cleanupwindow = function () { lz.Browser.callJS("window.close()"); } LzTimeKernel.setTimeout(cleanupwindow, 3000); } this.testStartTime = ((new Date)['getTime']()); //var testcase = "testcase: "+tc+" "+((((new Date)['getTime']()) - this.testStartTime)); //this.resultstring += (testcase+"\n"); //this.resultstring += ("failure: "+msg+"\n"); this.tests.length) { this.onsuitefinish.sendEvent(this.result.numFailures() > 0 ? 'fail' : 'pass'); return false; } var tc = this.tests[v]; var i = this.nextTest++; if (typeof(tc) == "undefined" || (i >= tc.length)) { this.nextCase++; this.nextTest = 0; } else { dw("subviews[", v, "].run(", this.result, ", ", tc[i], ");"); this.onteststart.sendEvent(tc[i]); subviews[v].run(this.result, tc[i]); this.ontestfinish.sendEvent([tc[i],this.result.numFailures() > 0 ? 'fail' : 'pass']); } var c = new LzDelegate( this , "runNextTest" ) lz.Idle.callOnIdle(c); return true; ]]> tests.append(theTest); A view that comprises a suite of LZUnit tests.

This is the LZUnit library. LZUnit is an implementation of the xUnit testing framework for LZX programs (cf., JUnit A Cook's Tour).

Each of the xUnit components is implemented as an LZX tag with the corresponding name. Tests can be written by defining a subclass of TestCase and defining test... methods. A test suite can be created by enclosing any number of TestCases in a TestSuite.

The usual helper methods, assertTrue, assertEquals, assertWithin, assertSame, etc. are available for implementing the tests. (See the documentation of Test for a full list.)

An LZX program that consists of a TestSuite will, when loaded, automatically run all of its child TestCases and report the number of test cases run, the number of failures, and the number of errors. If any error occurs, an obvious error message is presented.

Below is a simple example of the use of LZUnit demonstrating a successful test, a failed test, and a test that causes an error.

You must run LZUnit with debugging on for it to detect errors.

For a more in depth discussion, please see the Developer's Guide.

addTest("testSuccess"); assertTrue(true); assertFalse(false); assertEquals(null, undefined); assertWithin(0, .001, .01); assertSame(null, null); assertNotSame(null, undefined); assertNull(null); assertNotNull(undefined); assertUndefined(undefined); assertNotUndefined(null); addTest("testFailure"); addTest("testError"); fail("This is an intentional failure"); error("This is an intentional error"); ]]>