AOSUnit is the newest member of the xUnit family of testing frameworks. It is a pretty close port of Smalltalk's SUnit to SmallScript. The port was done not only to provide developers with a tool for testing their SmallScript work, but also as a test to see how far one could go programming straight Smalltalk in SmallScript.
If you've already used SUnit (or one of its relatives, such as JUnit), you'll be up to speed in no time at all. On the other hand, if you're not familiar with unit testing using the xUnit family of tools, I'd recommend for you to look at one of the web sites devoted to extreme Programming (www.c2.com, www.xprogramming.com, www.junit.org, www.xpdeveloper.com) for more information.
Note: this document is a work in progress, and will ultimately be merged into the SmallScript documentation.
AOSUnit is delivered as a zip file. If all you want to do is use AOSUnit, just yank the AOSUnit.dll out of the zip file and copy it to your SmallScript /packages directory.
To create a test case, include AOSUnit.sts in your script file. You can do this in one of two ways:
Load module: AOSUnit.
include path: AOSUnit.
Then first define a subclass of TestCase, and after, write your test methods.
Load module: AOSUnit.
Class name: FooTestCase extends: TestCase {
Method [
testPass
self assert: true
]
Method [
testFail
self assert: false
]
Method [
testError
3 zork
]
}
There are a number of ways to run the tests. While developing AOSUnit, I found it most convienient to declare an eval block at the end of the file, and run all the tests.
[
FooTestCase runSuite.
]
If all tests pass, something like the following will be printed to stdout:
3 run, 3 passed, 0 failed, 0 errors
Of course, the example above won't pass, so you'll get this result:
3 run,
1 passed,
1 failed,
#testFail
1 error
#testError
Since SmallScript is a text- and file-based language, you (at least at this stage) have no IDE or debugger.If you want to run a single test that has an error, or that has failed, I'd suggest trying it the way I did it:
[
"Try this to run a single test case."
¦ test ¦
test := FooTestCase selector: #testFail.
test runCase.
]
This will run the test and provide a stack dump for debugging purposes.
Throw of: "TestFailure('assertion failed')"
--------------
Stack Walkback
--------------
[18] FOREIGN FUNCTION ({})
[17] AMLCompiler.metaclass#ConsoleMain ({AMLCompiler})
[16] AMLCompiler.metaclass#execCommandLine: ({AMLCompiler,
"E:\Smalltalk\SmallScript System\Bin\stsc.exe" -nologo
E:\Smalltalk\SmallScript\SSUnit\test.sts})
[15] AMLCompiler#execCommandLine:
({a AMLCompiler instance, "E:\Smalltalk\SmallScript System\Bin\stsc.exe"
-nologo E:\Smalltalk\SmallScript\SSUnit\test.sts})
[14] AMLCompiler#execCommandLine: ({a AMLCompiler instance,
"E:\Smalltalk\SmallScript System\Bin\stsc.exe" -nologo
E:\Smalltalk\SmallScript\SSUnit\test.sts})
[13] AMLCompiler#processCommands ({a AMLCompiler instance})
[12] AMLCompiler#invokeEvalMethods ({a AMLCompiler instance})
[11] IndexedSlotCollection#do: ({{a Method instance}, a Block instance})
[10] AMLCompiler#invokeEvalMethods ({a Block instance})
[9] Method#value ({a Method instance})
[8] DefaultProject.metaclass#immediate-method ({DefaultProject})
[7] TestCase#runCase ({a FooTestCase instance})
[6] Block#sunitEnsure: ({a Block instance, a Block instance})
[5] Block#ensure: ({a Block instance, a Block instance})
[4] TestCase#runCase ({a Block instance})
[3] TestCase#performTest ({a FooTestCase instance})
[2] FooTestCase#testFail ({a FooTestCase instance})
[1] TestCase#assert:description: ({a FooTestCase instance, false, aString})
[0] TestCase#signalFailure: ({a FooTestCase instance, aString})
Since we're living in text mode here, I found it convenient to steal an idea from JUnit, and allow assertions to support description strings. These methods are defined in the file TestCaseExtensions.sts, and currently consist of the following methods on TestCase:
The main method of interest is TestCase>>#assert:description:. This takes a String as second argument. Normally, if the test case fails, this string will be written to the Transcript. If you run the following small sample,
Load module: AOSUnit. Class name: FooTestCase extends: TestCase { Method [ testFail self assert: false description: 'A stupid mistake' ] }[ "Try this to run a single test case." ¦ test ¦ test := FooTestCase selector: #testFail. test runCase. ]
The top line of your stack dump will look like this:
Throw of: "TestFailure('A stupid mistake')"
Of course, this string can be constructed dynamically.
| e | e := 42. self assert: e = 23 description: 'expected 23, got ' e printString
You can choose whether to log by overriding #isLogging in your test case subclass, and choose where to log to by overriding #failureLog.
The second add-on is the resumable TestFailure. Why would you need this? Take a look at this example:
aCollection do: [ :each | self assert: each isFoo]
In this case, as soon as the first element of the collection isn't Foo, the test stops. In most cases, however, we would like to continue, and see both how many elements and which elements aren't Foo. It would also be nice to log this information. You can do this in this way:
aCollection do: [ :each | self assert: each isFoo description: each printString, 'is not Foo' resumable: true]
This will print out a message on the Transcript for each element that fails. It doesn't cumulate failures, i.e., if the assertion fail 10 times in your test method, you'll still only see one failure.
During the port of AOSUnit, I noticed a number of methods defined in the x3j20 ANSI Smalltalk standard which weren't (yet) implemented in SmallScript. I'm collecting these (and other) ANSI Smalltalk standard methods in the ANSISmalltalk module.
This document Copyright © 2001 by Joseph Pelrine and MetaProg GmbH. All rights reserved. SmallScript is a registered trademark of SmallScript LLC. Name and Logo used with permission of David Simmons and SmallScript LLC.