Configure Reality

Configuration in enterprise application is a fact of life.
My take on it is as follows: delay it as late as possible and hard-code in anger until someone asks for a change. Then, accepting the fact that is likely to happen again, the value is pushed into configuration.

Configuration can be as simple as a collection of key-value pairs. But sometimes, configuration needs to be more complex than key-value pairs; just imagine some sort of hierarchical structure or a simple collection of related values.

For key-value pairs we can use the old appSettings section, with all its type un-safety (sorry, I forgot type-unsafe is derogative, nowadays we use dynamic to name things that blow up at runtime) and if we cannot wait so long and be old-fashioned and get earlier feedback from tools/compiler (boo!) application settings is another option.
For those edge cases in which more complexity is actually needed, there is also a wealth of information about how to use XML serialization to use strongly-typed classes, including various very last configuration sections ever to be written.

My preferred option, though, is using custom configuration sections, taking advantage of the well-thought set of classes living in System.Configuration. It is a pretty extensive topic which other people have covered in detail, but basically revolves around custom classes decorated with attributes with a semantic model that can be used to improve performance and live outside the rigid world of what can be done within attributes.

Dirt goes Under the Rug

The very fact that I am writing about it and deserves a place inside a testing library means that, in terms of testability, configuration can give you some headaches if configuration is used all over the place.

I consider configuration an infrastructure concern and as such I tend to abstract it away behind a interface; so consumers of configuration do not need to deal with configuration’s testing challenges.

But what happens when we need to test that our configuration object hierarchy is indeed correct? Furthermore, imagine for a second that your application behavior can be somehow tweaked once deployed by changing configuration values. What if those changes are done by people different from the ones that developed the application (or maybe the forgetful same ones)? What if, {insert_deity} forbids, whoever touches configuration makes a mistake? We would definitely want the error message to be descriptive. Or maybe we just want to provide some executable documentation about the usage of our configuration section and get some regression tests for free in the process.

My point is that we want to test not only the consumers of configuration (providing a test double), but the providers of configuration as well.

Behave!

Fact is that, regardless of the configuration strategy being used, only one set of configuration values tends to be loaded into the application. And such model does not really lend itself to testing multiple cases. The solution is simple enough: the class that provides the configuration can be instantiated differently while testing from the way is instantiated during application runtime. The trick is using an external configuration file via ConfigurationManager.OpenExeConfiguration(string).

Let’s exemplify with a complex configuration of a caching system in which each cache has a different time-to-life for its elements and each cache can have dependencies with other caches. The abstraction for this piece of configuration could be something like:

public interface ICachingConfiguration
{
TimeSpan TimeToExpire(string cacheName);
bool HasDependencies(string cacheName)
IEnumerable&lt;<string> GetDependantCaches(string cacheName);
}

The class implementing the abstraction, uses a bunch of collection elements “hanging” from a custom ConfigurationSection, the CachingConfigurationSection. The default constructor will be used when the provider is instantiated “normally”, but the constructor receiving the path of an executable (or assembly) file is used during testing to inject a file that contains the configuration for the scenario being tested.

public class CachingConfiguration : ICachingConfiguration
{
private readonly CachingConfigurationSection _section;
public CachingConfiguration()
{
_section = (CachingConfigurationSection)ConfigurationManager.GetSection(CachingConfigurationSection.SectionName);
}
public CachingConfiguration(string configFile)
{
_section = (CachingConfigurationSection)ConfigurationManager.OpenExeConfiguration(configFile).GetSection(CachingConfigurationSection.SectionName);
}
public TimeSpan TimeToExpire(string cacheName)
{
ExpirationElement expiration = _section.Expirations[cacheName];
if (expiration == null) throw new ConfigurationErrorsException();
return expiration.Value;
}
public bool HasDependencies(string cacheName)
{
DependenciesCollection dependencies = _section.Dependencies;
return dependencies != null &amp;&amp; dependencies[cacheName] != null;
}
public IEnumerable&lt;<string> GetDependantCaches(string cacheName)
{
IEnumerable&lt;<string> cacheNames = Enumerable.Empty<string>();
if(HasDependencies(cacheName))
{
cacheNames = _section.Dependencies[cacheName].DependantCaches
.Cast<DependantCacheElement>()
.Select(dependant => dependant.Name);
}
return cacheNames;
}
}

Find and Fetch

So the provider can receive a path from a file… Other part if the puzzle is a helper class that allows a declarative way of expressing the configuration file to be loaded. That part is achieved by decorating our tests with ConfigurationAssemblyAttribute, specifying the path to an imaginary assembly named after the file that contains the piece of configuration to be tested. For instance, a configuration file named CachingCorrectConfig.dll.config can be loaded by decorating the test with the path to an assembly CachingCorrectConfig.dll. That assembly file does not even need to be a real assembly, it suffices being an empty file with the .dll extension. In order to ease the retrieval of the attribute the ExternalConfiguration.GetConfigurationAssemblyPath() method can be used within the test. Let’s see how easy it is:

[Test]
[ConfigurationAssembly("..\\..\\Configuration\\ConfigFiles\\CachingCorrectConfig.dll")]
public voidGetConfigurationAssemblyPath_TestDecoratedWithAssemblyFile_AllowsAccessToConfigurationValuesFromCustomConfiguration()
{
string assemblyPath = ExternalConfiguration.GetConfigurationAssemblyPath(MethodBase.GetCurrentMethod());

ICachingConfiguration subject = null;
Assert.That(() => subject = new CachingConfiguration(assemblyPath), Throws.Nothing);
<Assert.That(subject.TimeToExpire("expiration1"), Is.EqualTo(1.Seconds()));
Assert.That(subject.HasDependencies("cache2"), Is.True);
Assert.That(subject.GetDependantCaches("cache1"), Is.EqualTo(new[]{"cache1_1", "cache1_2"}));
}

When Things Go Wrong

A way to tell people what to do is telling them what NOT to do. That works for testing as well. We need to verify that our assumptions on what we consider incorrect configuration are indeed correct (weird stuff). Problem is, error messages are localized and we do not really want to couple the success of a test of ours to a text on a third party resource (the framework is a “third party” in this case).
Well, all of my machines run English versions of both OS and .NET Framework so I think it is OK that my default error message fragments are in that language. If that is not the case for you, the same extensibility model as for the Must provider is used, so feel free to provide your own extension method for whichever case you feel like testing to ExceptionMessagePart.For:

[Test, ConfigurationAssembly("..\\..\\Configuration\\ConfigFiles\\CachingIncorrectConfig_UndefinedElement.dll")]
public void UndefinedElement_ExceptionWithMessageContaining()
{
string assemblyPath = ExternalConfiguration.GetConfigurationAssemblyPath(MethodBase.GetCurrentMethod());
Assert.That(() => new CachingConfiguration(assemblyPath), Throws.InstanceOf<ConfigurationErrorsException>()
.With.Message.StringContaining(ExceptionMessagePart.For.UndefinedElement(ExpirationElement.ElementName)));
}
[Test, ConfigurationAssembly("..\\..\\Configuration\\ConfigFiles\\CachingIncorrectConfig_MissingRequiredElement.dll")]
public void MissingRequiredElement_ExceptionWithMessageContaining()
{
string assemblyPath = ExternalConfiguration.GetConfigurationAssemblyPath(MethodBase.GetCurrentMethod());
Assert.That(() => new CachingConfiguration(assemblyPath), Throws.InstanceOf<ConfigurationErrorsException>()
.With.Message.StringContaining(ExceptionMessagePart.For.MissingRequiredMember(ExpirationsCollection.CollectionName)));
}

I Want It!

You got it! It is in Testing.Commons! Binary or packaged, your choice.

Have many ideas for improvement? Share them and contribute to the project. Let the code speak.

Daniel Gonzalez Garcia
Vertica A/S

Skriv et svar

Udfyld dine oplysninger nedenfor eller klik på et ikon for at logge ind:

WordPress.com Logo

Du kommenterer med din WordPress.com konto. Log Out / Skift )

Twitter picture

Du kommenterer med din Twitter konto. Log Out / Skift )

Facebook photo

Du kommenterer med din Facebook konto. Log Out / Skift )

Google+ photo

Du kommenterer med din Google+ konto. Log Out / Skift )

Connecting to %s