Taking the lid off a large application


For the past 2+ years, a great deal of my energy has been spent on an application called Enterprise Tester.

Enterprise Tester is a Quality Management tool developed by Catch Software here in Auckland, New Zealand - but used around the world from small to very large QA teams.

Codebase

The application is developed in .Net and currently consists of 4,865 source files and 491,809 lines of code (including tests, comments and blank lines) - and currently targets the .Net Framework 4.0.

The client-side code is mostly JavaScript, and weighs in at 257 files and 174,785 lines of application-specific code (excluding 3rd party libraries such as jquery).

What is it?

Quality Management Tools (more traditionally known as Test Management Tools) are applications or suites of applications used by QA teams to ensure a level of quality in a product (often software). Using a combination of manual testing and automated testing (to determine the level of quality/maintain a level of quality) and then relating those tests etc. back to features (requirements/user stories/epics/use cases) to then establish the level of both coverage and impact/risk associated with a change.

Probably the most familiar tool in this category to many people, especially if you have been around for a while is HP Quality Center (Originally Mercury Quality Center).

What's next

Over the next few weeks I'm going to looking at some of the more Interesting features of Enterprise Tester (from a technical perspective) that I have been involved in implementing, and taking a bit of deep dive on their implementation "under the hood".

This first thing I plan to review is the recent introduction of ASP.Net WebAPI to implement our REST API, which hopefully should be interesting to anyone working with long-lived codebases / brown-field applications where they are looking to retro-fit an API.

But before diving into implementation I thought it might be worth providing some context around the products overall structure.

Structure

Currently the implementation consists of a:

  • Core Web Project
  • 2 Core Assemblies (EnterpriseTester.Common and EnterpriseTester.Core)
  • A set of plugins, which have inter-dependencies between each other (generally each plugin is a single assembly).
  • Everything is wired together by an IoC container (Castle Windsor) + MEF (for discovery and loading of the "plugin installers" that exist in each plugin assembly).
  • We make heavy use of a concept called modules, which predates MEF (did I mention brown fields?) - A module is an interface which inherits from this Interface:

public interface IModule
{
bool IsEnabled { get; }
void SetEnabled(bool isEnabled);
}

There is then a matching interface that components can implement which allows them be "aware" of modules being registered, or unregistered.

public interface IModuleAware
{
void ModuleRegistered(TService instance);
void ModuleUnregistered(TService instance);
}

This basic mechanism is then used to compose the features of the application - so for example we have an interface that toolbar items implement in the application:

public interface IToolbarItemDefinition : IModule
{
int Order { get; }
bool Supports(IQueryContext context);
IToolbarItemPresenter GetPresenter();
}

And we then have a class which implements a registry for these toolbar "modules" which collects them, and provides in this case a way to get back the toolbar items that a relevant to a kind of query being executed (these are toolbar items that belong to a grid of search results).

public class ToolbarItemDefinitionRegistry : IModuleAware, IToolbarItemDefinitionRegistry
{
readonly IList _definitions = new List();

public void ModuleRegistered(IToolbarItemDefinition instance)
{
_definitions.Add(instance);
}

public void ModuleUnregistered(IToolbarItemDefinition instance)
{
_definitions.Remove(instance);
}

public IList GetAllDefinitions()
{
return _definitions.ToList();
}

public IList GetDefinitionsFor(IQueryContext context)
{
return _definitions.Where(def => def.Supports(context)).OrderBy(def => def.Order).ToList();
}
}

This approach to modules is generally how individual bits of functionality have been kept insulated and allowed for feature switching at run time as well. A Facility within our IoC container then takes care of detecting when a module has all it's dependencies satisfied and can be created and added to the module aware component.

This should be familiar to anyone working with MEF Today where you can use ImportMany to achieve something similar - the only difference in this case is that our implementation allows for control over if a module can be loaded (so we can store enabled/disabled state permanently for each module across application restarts, or prevent a module from loading if some requirement is not met i.e. Licensing).

So here we can see a list of all the plugins which are currently loaded - at the plugin level we manage both dependencies between plugins (so for example we have an Automated testing plugin, that then provides the framework for automated test tool adapter plugins to load, such as Selenium, XUnit etc.) and Licensing concerns (so not loading plugins, if a valid license for that plugin does not exist or has expired).



And you can also manage which individual modules are enabled/disabled - which is useful if you want to soft-launch new features in the product, or provide a mechanism for Administrators to remove functionality from within the application.



Storage

The application is fairly traditional with data storage being to Sql Server, MySql, PostgreSql or Oracle - the application uses an ORM - Castle Active Record (NHibernate) - to handle persistence of data to the database.

Searching of data within the application is handled via an application-specific search and query implementation built on top of Lucene.

I'll hopefully also cover some of the implementation specifics there in a future post.

Front-end

The front-end of the application is built using Sencha's ExtJS and is for the most part implemented as a single page application - there is a plugin implementation for the front-end UI as well, and a simple pub/sub implementation is used to allow different parts of the application to respond to various events.

Communication of the client-side to the server is almost exclusively performed via JSON.

Disclaimer

Though I work for Catch Software, I'm publishing this series on my personal blog - so any opinions expressed here are strictly my own and not those of Catch Limited New Zealand (and certainly are likely to fall out of date after publishing this series) - for the official word on Enterprise Tester, please instead check out the company blogs - http://blogs.catchsoftware.com or the website http//www.enterprisetester.com

Catch Software is hiring

Catch Software is needing to expand the development team and are looking for a passionate senior developer, or highly motivated intermediate developer looking to move into a senior position.

You would be working on the Enterprise Tester application (as well as other existing and new products Catch Software are developing) alongside myself and the rest of the great Catch Software team - you can find more about the job offer here.

Written on August 20, 2012