Specifying Types for Smalltalk Fit

I maintain the Smalltalk ports of the Fit,
Fitnesse, and
FitLibrary acceptance
testing tools. These tools allow you to write acceptance tests for
your software by using HTML tables.

I plan to write more about the Fit family of tools and their use in
Smalltalk in the future, but for this post, I want to talk about an
implementation detail of some recent work I’ve done on this project.

In the Fit family of tools, the test tables are interpreted by
“fixture code” in your programming language of choice. The fixture
code runs the tests against your application. The test results are
used to mark up the test tables to show passes, failures, and
exceptions.

In order for this table interpretation process to work, the test data
and expected results need to be converted from the text or HTML in
table cells to the correct data types in the programming language. The
types include “primitives” such as numbers, strings, and booleans, but
also more complex custom domain types.

In an explicitly-typed language like Java, the table interpreter
can use reflection to access the type information. But in an
implicitly-typed language like Smalltalk, the type information needs
to be provided a different way.

Initially, I solved the type information problem by requiring every
fixturing class to implement an instance-side method named
#signatureFor:. The framework sends this message with a selector as
an argument, and the method must return an appropriate
MethodSignature or something that can be converted to one, like a
class. This works fine, but these methods are tedious to write and
they turn into big case statements for any Fixture class of moderate
size. Also, the syntax for creating a MethodSignature for several
argument types and a return type is quite verbose. Here’s a
relatively small example of a signatureFor: method:

DoAccount>>signatureFor:

signatureFor:aSymbol

(#(#addCategories#modifyCategories#saveAccount#clearAccount

#reloadAccount#deactivationRule)

includes:aSymbol)ifTrue:[^Fixture].

aSymbol==#makeBatchWithdrawalOn:note:

ifTrue:[^MethodSignaturewith:Datewith:Stringreturning:Fixture].

^MethodSignaturewith:String

Several months ago, I decided to try a different solution based on
Visualworks Smalltalk’s pragma feature.
Pragmas are similar to annotations in Java in that they allow you to
add declarative metadata to methods. The main limitation with pragmas
as implemented in Visualworks is that they can only contain literals.
This limits flexibility a bit when specifying parameter and return
types. Fortunately, Visualworks has a literal syntax for binding
references, #{Boolean}, which saves the day. Using pragmas, we can
eliminate the #signatureFor: method above in favor of annotations
like this:

DoAccount>>makeBatchWithdrawalOn:note: (with binding references)

makeBatchWithdrawalOn:aDatenote:aString

<fitTakes: #(#{Date} #{String})>

<fitReturns: #{Fixture}>

^(BatchWithdrawalFixturedate:aDatenote:aString)

systemUnderTest:systemUnderTest

Now the type information is right in the method rather than off to
the side somewhere.

This solution works well for most situations, though the literal
binding reference syntax can get noisy when some of the parameters are
collections.

Recently, when trying to understand how SUnitToo’s test resources
work, I stumbled across TestCase>>methodLocalResources, which has
the following bit of code in it:

TestCase>>methodLocalResources (fragment)

classRefisSymbol

ifTrue:

[classRef:=

BindingReferencesimpleName:classRefin:methodenvironment].

This is a way to turn a Symbol into a BindingReference using the
compilation environment of the method containing the pragma. As a
side note, this compilation environment takes into account any default
package namespace, which is what we want.

Using this trick, it is now possible to use symbols as type specifiers
instead of literal binding references in almost every case.

DoAccount>>makeBatchWithdrawalOn:note: (with binding references)

makeBatchWithdrawalOn:aDatenote:aString

<fitTakes: #(#Date #String)>

<fitReturns: #Fixture>

^(BatchWithdrawalFixturedate:aDatenote:aString)

systemUnderTest:systemUnderTest

All three of these mechanisms are supported in the current
implementation. I recommend the following heuristics for choosing
among them:

Use pragma-based type specifications with symbols where possible.

If the class name you need is not in the method’s environment,
you’ll need to use a literal binding reference with a
fully-qualified class name instead (e.g #{Fit.Fixture}).

If you use FitLibrary’s ability to work directly with domain
objects, you’ll often need to add type information for methods on
your domain classes. While the pragmas are harmless, you may not
want to have them in your domain code. In that case, you can add
#signatureFor: to your domain class as an extension method that
lives in your fixture package. As an example, some of the
fixturing code that supports the FitLibrary specifications uses
base Smalltalk classes directly. Since I don’t want to override
methods in the base code to add type information, I use a
#signatureFor: extension method instead.

If you need to specify a complex method signature that can’t be
handled by literals in a pragma, use the #signatureFor: approach.
I have only one case like this, and it’s there to support an arcane
Java-specific feature of the FitLibrary specification tests.