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