Tuesday, October 20, 2009

DataWings: Convention over Configuration

I've just released some new functionality for the increasingly popular (?) framework for data driven integration testing: DataWings. This new functionality aims at letting you register conventions as to how your domain entities map to the database tables, and in this way you can declare your assertions in a much more concise and elegant manner. The purpose is to reduce the amount of ceremony needed to execute the assertions.

Get the bits here.

An example:

The Old Way:

   1:  [Test]

   2:  public void CreatePersonInTransaction_ScopeCompleted_PersonExistsInDatabase()

   3:  {

   4:      Person person;

   5:      using (var scope = new TransactionScope())

   6:      {

   7:          person = new Person{ Id = Guid.NewGuid() };

   8:          IoC.GetInstance<IDomainObjectProvider>().Save(person);

   9:          scope.Complete();

  10:      }

  11:      // Assertion using DataWings

  12:      // The old fashioned way

  13:      DbAssert.ForTable("PERSON")

  14:          .WithColumnValuePair("ID", person.Id)

  15:          .Exists();

  16:  }

The New Way (using conventions):

   1:  [Test]

   2:  [DbTableNameConvention(DbTableNameConventionType.ClassNameEqualsTableName)]

   3:  [DbIdConvention("ID")]

   4:  public void CreatePersonInTransaction_ScopeCompleted_PersonExistsInDatabase()

   5:  {

   6:      Person person;

   7:      using (var scope = new TransactionScope())

   8:      {

   9:          person = new Person{ Id = Guid.NewGuid() };

  10:          IoC.GetInstance<IDomainObjectProvider>().Save(person);

  11:          scope.Complete();

  12:      }

  13:      // Assertion using DataWings

  14:      // The new way with conventions

  15:      DbAssert.Exists(person);

  16:  }

All conventional assertions (such as the one on line 15) are available as extension methods, so that the code on line 15 can be replaced with this code:


Attribute based configuration

Notice how the assertion on lines 13 to 15 in the “old way” is replaced by just a single line (number 15) in the sample using conventions. In order to use this concise notation, DataWings will have to know how to map the object to its corresponding table, i.e. what the conventions are. These conventions are specified with the help of attributes, and in the sample above you can see how the two attributes DbTableNameConvention and DbIdConvention set up such conventions.

Such attributes may be put on methods or classes, the functionality walks the stack looking for an attribute to use. This is done by examining the method and class of each stack frame until a suitable attribute decoration is located. If no suitable attribute can be found, the conventional convention (see below) will be used.


The conventional conventions

You can start using the conventions functionality immediately, if you accept the conventional conventions, that is. And these conventions are:

  • Table name equals name of class

  • Name of unique key column in table is on the format Id[ClassName]

Registering conventions for mapping class to table

The convention for how the class maps to a database table is specified through the usage of DbTableNameConvention. The following example show how to use this attribute:

Class name and table name match exactly


Class name as part of the table name

[DbTableNameConvention(DbTableNameConventionType.Custom, Convention = "TBL_{0}")]

Overriding for specific classes

The attribute also has a property EntityType, and this is used in cases where the conventions for a specific class do not match the conventions of the other entities that are in play in the test.

[DbTableNameConvention(DbTableNameConventionType.Custom, Convention = "TBL_{0}", EntityType = typeof(Address))]

[DbTableNameConvention(DbTableNameConventionType.ClassNameEqualsTableName, EntityType = typeof(Relation))]

Registering conventions for mapping primary key

The convention for how the primary key of the table is  mapped to a property of the class is specified by using DbIdConvention. Some examples:

Name of primary key column is identical for all tables


Class name is part of primary key column name