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: [^MethodSignature with: Date with: String returning: Fixture].
^MethodSignature with: 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: aDate note: aString
<fitTakes: #(#{Date} #{String})>
<fitReturns: #{Fixture}>
^(BatchWithdrawalFixture date: aDate note: 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)
classRef isSymbol
ifTrue:
[classRef :=
BindingReference simpleName: classRef in: method environment].

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: aDate note: aString
<fitTakes: #(#Date #String)>
<fitReturns: #Fixture>
^(BatchWithdrawalFixture date: aDate note: aString)
systemUnderTest: systemUnderTest

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

  1. Use pragma-based type specifications with symbols where possible.

  2. 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}).

  3. 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.

  4. 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.