AOSUnit for SmallScript

Version 0.2, May 3, 2002

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.

Downloading and Installing AOSUnit

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.

Download AOSUnit.

Creating a TestCase

To create a test case, include AOSUnit.sts in your script file. You can do this in one of two ways:

  1. (The preferred way) Bind the DLL using:
    Load module: AOSUnit.

  2. Include the source, using the statement:
    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}) 

Extensions

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.

ResumableTestFailure

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.

ANSI Support

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.

The fine print

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.