Dynamic Type Issues


One of the main problems porting FIT to dynamically typed languages arises from the fact that a method's return type or a variable type is never statically known, but that information is needed by the internal mechanism of the framework to perform meaningful comparisons between expected results and computed results in an acceptance test's table. Much more than other examples, where this mechanism remains hidden, the ColumnIndex fixture clearly states the issue in terms of code, especially in the columnType column.

While the Java and C# FIT implementations (and every version written in a statically typed programming language with reflection features) heavily relies on that kind of information during computation, dymanically typed programming languages need to come up with a support system able to provide those information on methods and fields which is typically not available at runtime. This page is intended as a place where developers of dynamic languages ports (Smalltalk, Perl, Python, Ruby, et cetera) can explain their strategy, and some guidelines about such support systems can be extracted.


Ruby (written by GiulioPiancastelli)

There are essentially two places where data about the return type of methods and the type of fields needs to be provided: fixtures (e.g. ColumnFixture) and domain objects which are to be inspected by a RowFixture. The Ruby port of FIT provides such information in the form of a static hash called metadata. Hash keys are strings representing header names in HTML tables; hash values are classes representing types.

Metadata can be influenced by the TypeAdapter mechanism. While in the Java implementation there exists a different type adapter for each primitive type and for arrays, in Ruby a more generic adaptation mechanism has been used. That is, only a single generic adapter exists, able to recognize arrays and primitive types like boolean or numbers. As a downside, this has the negative effect of complicating the code in the TypeAdapter class. But as an upside, this has the positive effect of lighten the burden of metadata augmentation: in fact, if field and methods return types belong to those types already recognized by the generic adapter, you don't need to provide their metadata.

For example, just imagine to use the Music class in the MusicExample example with the Display RowFixture only. It inspect various fields and methods of Music objects; but of those fields and methods, only date (which appears in the very last table) belongs to a type not recognized by the generic adapter. Thus, the only metadata you'd need to provide in the Music class would be the following:

  @@metadata = { 'date' => Time }

What about the ColumnIndex example? First of all, it has been rewritten to work with Ruby classes: so, instead of Java classes from the package java.lang, primitive types like int and double, and the java.util.Date class, the example now uses String, Object, Fixnum, Float, and Time. Then, metadata has been added to the appropriate objects.

Being ColumnIndex a RowFixture, data about types does not belong to it. Since a RowFixture is used for inspection of another object, which type is returned by the getTargetClass method in the RowFixture class, it is that object that must be augmented with metadata. Thus, mimicking the Java implementation, here is the relevant method in the ColumnIndex fixture:

  def get_target_class
    Column
  end

And, here is the metadata support added to the Column class:

  @@metadata = { 'column_name' => String, 'column_type' => Class,
		 'column' => Object, 'class_name' => Class }
  def Column.metadata
    @@metadata
  end

But ColumnIndex is used also to inspect a Music object from the MusicExample example: more, it is aimed at inspecting the structure of the object instead of its values. In the same fashion, a complete set of metadata has been added to the Music class:

  alias to_string to_s
  @@metadata = { 'title' => String, 'artist' => String, 'album' => String,
		 'genre' => String, 'size' => Fixnum, 'seconds' => Fixnum,
		 'track_number' => Fixnum, 'track_count' => Fixnum,
		 'year' => Fixnum, 'date' => Time, 'track()' => String,
		 'time()' => Float, 'to_string()' => String
  }
  def Music.metadata; @@metadata; end

An alias has also been added to avoid the mismatch between the names of the method for the canonical converter to string representation in Java and Ruby.

Let's now take the GeoCoordinateExample to see how to add metadata support in the case of a ColumnFixture. Data cells all holds types recognized by the generic adapter except for the columns representing coord and coord2 input fields, and the column representing the coord() output method: those hold GeoCoordinate objects, and metadata for them needs to be provided in order to have the framework perform meaningful comparisons between expected and computed test results. Thus, the GeoCoordinate ColumnFixture class must be augmented with an hash called metadata: keys are strings representing column headers in HTML tables; values are classes representing types of methods and fields. The relevant part of the fixture class resembles the following:

  class GeoCoordinateExample < Fit::ColumnFixture
    @@metadata = { 'coord' => GeoCoordinate, 'coord2' => GeoCoordinate,
		   'coord()' => GeoCoordinate
    }
    def GeoCoordinate.metadata; @@metadata; end
    # ...
  end

Note that even if a Ruby method can be defined avoiding the use of parenthesis, the string used as key in the metadata hash must exactly match the column header in the HTML table instead of the name of the method defined in the class. Under the hood in the framework engine, first the matching between column header and key in the metadata hash is performed; then, when it comes to actually call the method, another mechanism ensures the matching between column header and method name is successfully executed.

To understand how metadata in ActionFixture works, you need to remember that this particular kind of fixture interprets rows as a sequence of commands directed to an instance of another class, typically itself another fixture. Thus, it is this latter class, called an actor, which needs to contain metadata for checking test results.

Let's take NetworkExample as a brief case study. The ActionFixture drives actions towards the Simulator class which contains appropriate metadata. Since the only type it works with which is not recognized by the generic TypeAdapter is GeoCoordinate, metadata needs to be provided only for those fields and methods which directly deals with instances of that class.

Finally, TimedActionFixture is a particular breed of ActionFixture which adds two output columns representing time laps, and as such does not need any additional information about providing metadata with regards to what has already been said about ActionFixture.

As a reference, you can look at FixturesInExamples if you are interested in details about which fixtures are exercised in which example.

 

Last edited October 25, 2005
Return to WelcomeVisitors