Hello.
I have moved the test framework over to its own post, as it is really a stand-alone thing.
It is very easy to use, and can be used for a variety of stuff, like writing a test suite, that you can program towards, so that at least all your handlers are ready when all the tests are passed.
Or as a final integration test, that you run the test suite after you have made changes, to check that everything still works as it should.
Maybe you’ll have to play a little bit with this to grasp it, but it should be fairly simple, and should pay off really well.
You’ll need to have installed getMillisec in order to use this, and know that it is working.
We are not using millisecs in these tests, we are on the other hand using ticks:
Ticks are better! milliseconds / 10 = hundreths, there can be 100 ticks in a second, if the form of ticks you deal with is of this kind.
If you really want to deal with ticks of the old kind which where 1/60th of a second, then you can divide the milliseconds by 100 and multiply by 6, which is what I have done here, to provide for some consistency
Maybe you’ll get the idea of how you can utilize the test library, by reading through the code below, I think you will.
Below are some references, before the Library script that you’ll have to install in your Script Libraries folder, and after that a short demo script, to give you some ideas.
And there are handlers in the Script Library, that makes it really easy to test out algorithms.
References
This an in-depth article about AppleScript’s Variable types and you. where I stumbled upon the test framework.
The test script I found here has a caveat though. Be warned: you must remove all calls and references to simpleAddSelfToEndOfSelfErr() since it will crash AppleScript editor!
If you want to know slightly more about unit testing, then this article may be of great interest.
There is also a page or two about the same test framework over at DrDobbs but be prepared to give away your email address in order to create an account/sign in.
About the code:
This code is shamelessl stoleny from Ryan Wilcox, (see reference above). As usual all faults are mine.
This script doesn’t have to be saved as a bundle. No terminolgy here either, as this is for development.
I hope you find it useful.
-- This is totally stolen from Ryan Wilcox, and reworked, just a little bit
-- so that the handler for ticks has been replaced with getMillisec
-- and that this code has been prepared for AppleScript version 2.3
-- I have also removed his test results for the various handlers.
-- I have changed the usage of testHandlers into test scripts, to make it easier
-- to reuse, I have have also done any modifications necessary so that
-- the framework can be put into an AppleScript 2.3 Library.
-- I have called the Library "RyanWilcox" and uses
--
-- use Wilcox : script "RyanWilcox"
--
-- to call it. (This Library should be saved as a script file in your
-- ScriptLibraries folder.) See example below for usage:
-- The idea is that you write a test script (at least, for testing one of
-- your handlers, whether it be in a regular script or library, in a
-- *separate script* that you run to test the integrity of your library/script
-- as you add more handlers, or change stuff.
-- You'll get the hang of it, if this is something that suits you! ;)
-- Rev2: added a method to run many test suites: there can be many suites in
-- one unit.
-- rev3: Added a method to log stuff _log, removed the printing of fail, when
-- nothing has failed. Optimized the "printing" with TextEdit.
-- The window with the results now pop up automatically over the test script that ran.
property parent : AppleScript
use AppleScript version "2.3"
use scripting additions
use McUsr : script "TimerTools"
use output : script "Output"
property numberTestsThatHaveRun : 0
property numberTestsThatHavePassed : 0
property numberTestsThatHaveFailed : 0
property processFile : 0
-- ---------------------- User control properties ---------------------- --
property doTimings : true
-- ----------------------- Utility Functions ---------------------- --
to getMinimumOf(lst)
set currSmaller to item 1 of lst
repeat with each in lst
if (each < currSmaller) then
set currSmaller to each
end if
end repeat
return contents of currSmaller
end getMinimumOf
to getMaximumOf(lst) (*950*)
set currSmaller to item 1 of lst
repeat with each in lst
if (each > currSmaller) then
set currSmaller to each
end if
end repeat
return contents of currSmaller
end getMaximumOf
on getAverageOf(lst)
set total to 0
set nums to number of items in lst
repeat with each in lst
set total to total + each
end repeat
set average to total / nums
return average
end getAverageOf (*969*)
to _result(beforeText, testName, afterText)
local testInfo
set testInfo to beforeText & testName & " (" & afterText & ")"
output's writeLn(testInfo)
end _result
-- ----------------------- Framework stuff ---------------------- --
-- Novelty: _log statement:
on _log(theText)
output's writeLn("==> " & theText)
end _log
on _test(testPassed, testName) (*979*)
set numberTestsThatHaveRun to numberTestsThatHaveRun + 1
set textStatus to ""
if (testPassed) then
set numberTestsThatHavePassed to numberTestsThatHavePassed + 1
set textStatus to "passed"
else
set numberTestsThatHaveFailed to numberTestsThatHaveFailed + 1
set textStatus to "failed"
end if
_result("tested: ", testName, textStatus)
end _test
on _time(iters, theTest, testName)
if doTimings then
set durations to {}
repeat with i from 1 to iters
set startTime to the (McUsr's getMillisec()) / 10 * 6
run theTest
set endTime to (McUsr's getMillisec()) / 10 * 6
set ourDuration to endTime - startTime
set end of durations to ourDuration
end repeat
set minTime to getMinimumOf(durations)
set avgTime to getAverageOf(durations)
set maxTime to getMaximumOf(durations)
_result("timed: ", testName, "min: " & minTime & ", max: " & maxTime & ", avg: " & avgTime)
else
_result("timing for: ", testName, "skipped")
end if
end _time
to _timeTest(iters, theTest, testName)
_test(run theTest, testName)
_time(iters - 1, theTest, testName)
end _timeTest
on initFramework()
output's reset()
output's init(("Test Run starting at " & (current date) as string) & return, 600, 800)
output's bringToFront()
end initFramework
to displayFinalMessage(msg, unitName)
display notification msg with title "Wilcox's Testsuite" subtitle unitName
end displayFinalMessage
on terminateFramework()
output's writeLn(return & return & ¬
"===============================" & return & "Number of tests ran: " & numberTestsThatHaveRun ¬
& return & "Number of tests failed: " & numberTestsThatHaveFailed)
--take care to reset our properties
set numberTestsThatHaveRun to 0
set numberTestsThatHavePassed to 0
set numberTestsThatHaveFailed to 0
set testRunner to missing value
output's leaveAtFront(2)
end terminateFramework
on runTestSuite(testsToRun, unitName)
initFramework()
run testsToRun
set messageStr to ((("Ran " & numberTestsThatHaveRun as string) & " tests. " & numberTestsThatHavePassed as string) & " passed, " & numberTestsThatHaveFailed as string) & " tests failed."
tell me to activate -- switch back to this app for the user
displayFinalMessage(messageStr, unitName)
terminateFramework()
end runTestSuite
-- You are suppose to initialize the framework up front
-- and take it down afterwards.
-- we do keep track of the total number of tests and everything here.
on runManyTestSuites(testSuitesToRun, testSuitNames, unitName)
local totRuns, totPassed, totFails, idx
set {totRuns, totPassed, totFails, idx} to {0, 0, 0, 1}
initFramework()
repeat with testsToRun in testSuitesToRun
run testsToRun
-- Novelty: no printing of "0 failed"
if numberTestsThatHaveFailed > 0 then
set messageStr to ((("Testing " & item idx of testSuitNames & ":" & linefeed & "Ran " & numberTestsThatHaveRun as string) & " tests. " & numberTestsThatHavePassed as string) & " passed." & numberTestsThatHaveFailed as string) & " tests failed."
else
set messageStr to ((("Testing " & item idx of testSuitNames & ":" & linefeed & "Ran " & numberTestsThatHaveRun as string) & " tests. " & numberTestsThatHavePassed as string) & " passed.")
end if
-- _PRINT(return & messageStr & return)
output's writeLn(return & messageStr & return)
set {totRuns, numberTestsThatHaveRun, idx} to {totRuns + numberTestsThatHaveRun, 0, idx + 1}
set {totPassed, numberTestsThatHavePassed} to {totPassed + numberTestsThatHavePassed, 0}
set {totFails, numberTestsThatHaveFailed} to {totFails + numberTestsThatHaveFailed, 0}
end repeat
if totFails > 0 then
set messageStr to ((("Total: " & totRuns as string) & " tests." & totPassed as string) & " tests passed, " & totFails as string) & " tests failed."
else
set messageStr to ((("Total: " & totRuns as string) & " tests." & totPassed as string) & " tests passed.")
end if
tell me to activate -- switch back to this app for the user (that runs this script..)
displayFinalMessage(messageStr, unitName)
set {numberTestsThatHaveRun, numberTestsThatHaveFailed} to {totRuns, totFails}
terminateFramework()
end runManyTestSuites
This is an example simple usage, check out post #4 for a more realistic example.
use AppleScript version "2.3"
use scripting additions
use Wilcox : script "RyanWilcox"
-- this is a demonstration script of unit testing in AppleScript.
script mytest1
-- This would be a test for a real handler,
-- that you wrote a test for that returned true
-- if it passed, or false if it failed.
if (3 + 4) = 7 then
return true
else
return false
end if
end script
script mytest2
-- This would be a test for a real handler,
-- that you wrote a test for that returned true
-- if it passed, or false if it failed.
if (10 / 5) = 2 then
return true
else
return false
end if
end script
script testsToRun
-- Wilcox's currHandlerToTest
Wilcox's _test(run mytest1, "my first Test")
Wilcox's _test(run mytest2, "my second test")
-- demo of timing:
Wilcox's _time(100, my mytest1, "my first Test")
Wilcox's _time(100, my mytest2, "my second Test")
end script
-- This is a template function that is to be used in every test.
Wilcox's runTestSuite(testsToRun, "Concept Test")
Enjoy!