Many rules for a softwares architecture can be described by elaborating on dependencies between modules, submodules and types. Therefore this first shot concentrates on testing dependencies. The goal is to write simple test cases in the manner of unit tests and with the same tools (VisualStudio Quality Tool, NUnit, Mock frameworks). Automation is the key to success. Those test suites can be run during your build process within seconds and give immediate feedback.

Keep in mind: Successful validation is finding no evidence for breaking the defined rules. In contrast to normal testing we do not check for a predictable outcome. We just prove that our assumptions are not violated.

Enforce and Forbid References

This kind of test is suitable for checking the dependency rules between modules and namespaces. You want to ensure that parts of your software a completly independent from each other. Normally you will have more test that forbid a reference from Module1 to Module2. Rarely you will also define rules for mandatory dependencies – means Assert.IsTrue(Verifier.RefersTo…). RefersTo also throws a DependencyException when the specified type, modules or namespace are not part of the dependency model.

[TestMethod]
public void PrinterForbiddenProjectDependencies()
{
    Assert.IsFalse(Verifier.RefersTo(typeof(Printer.Properties.IAssemblyIdentifier).Assembly, typeof(CustomControls.Properties.IAssemblyIdentifier).Assembly));
    Assert.IsFalse(Verifier.RefersTo(typeof(Printer.Properties.IAssemblyIdentifier).Assembly, typeof(Scanner.Properties.IAssemblyIdentifier).Assembly));
    Assert.IsFalse(Verifier.RefersTo(typeof(Printer.Properties.IAssemblyIdentifier).Assembly, typeof(Communication.Properties.IAssemblyIdentifier).Assembly));
}

A namespace shall not access another


[TestMethod]
public void ExternalServicesDoesNotAccessServicesNamespace()
{
    var moduleDescriptor = DescriptorBuilder.BuildAssemblyDescriptor(typeof(Custom.Properties.IAssemblyIdentifier).Assembly);
    var externalService = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Custom.ExternalService");
    var services = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Custom.Services");
    Verifier.VerifyDoesNotReferTo(externalService, services);
}

Allow and Forbid Third Party Products

Including third party products leads to large setup and patches. Especially community products bear the risk of bugs you do not have under control and lack support. How stable are the APIs between major versions? Is the product actively maintained? And so forth. As software architect you have to choose foreign modules wisely. The VerifyForeignModules throws and exception when other foreign modules are referenced than specified. It also throws when the allowed modules are not referenced at all. Framework modules are not counted, but there is a method VerifyFrameworkModules for checking them.

[TestMethod]
public void VerifyAllowedForeignAssemblies()
{
    List<string> allowedAssemblies = new List<string> { "WPFToolkit", "RichHtmlTextBlock","log4net" };
    Verifier.VerifyForeignModules(allowedAssemblies);
}

Limit the Number of Accessors

When you consume external services you do not want to pass the external data transfer objects around in your application, because you might want to switch to another API or you have API changes. The abstraction of the external API is also relevant when you consume multiple external services that provide similar information, for example a build monitr for both TeamCity and Team Foundation Server. When writing unit tests you will be thankful for abstraction. Another use case is you want to forbid the access to system message boxes as you want to provide your own dialog for messages that follows the corporate design guidelines. Are you have implemented creational patterns, then for example you want only allow the factory to access a type constructor. The method for verification is VerifyNoOtherReferrers. Descriptors for members, types, namespaces and modules are allowed as referrers and target.

[TestMethod]
public void MessageBoxIsOnlyCalledByWindowOperations()
{
    var windowOp = DescriptorBuilder.BuildTypeDescriptor(typeof(WindowOperations));
    var messageBox = DescriptorBuilder.BuildTypeDescriptor(typeof(Custom.MessageBox));
    Verifier.VerifyNoOtherReferrers(new List<IDescriptor> { windowOp }, messageBox);
}
 
[TestMethod]
public void SystemMessageBoxIsNeverCalled()
{
    var messageBox = DescriptorBuilder.BuildTypeDescriptor(typeof(System.Windows.MessageBox));
    Verifier.VerifyNoOtherReferrers(new List<IDescriptor>(), messageBox);
}
 
[TestMethod]
public void ExternalServiceIsAccessedByWrapperOnly()
{
    var moduleDescriptor = DescriptorBuilder.BuildAssemblyDescriptor(typeof(Custom.Properties.IAssemblyIdentifier).Assembly);
    var externalService = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Custom.ExternalService");
    var services = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Custom.Services");
    Verifier.VerifyNoOtherReferrers(new List<IDescriptor> { services }, externalService);
}

Ensure Access via Interface

In order to decouple modules and submodules from each other you want them to access each other via interfaces. VerifyAccessType throws a DependencyException when the referrer accesses the target on a level other than defined. You may combine Interface, Abstract and Concrete.

[TestMethod]
public void ControlsAccessViewModelThroughInterface()
{
    var moduleDescriptor = DescriptorBuilder.BuildAssemblyDescriptor(typeof(Custom.Properties.IAssemblyIdentifier).Assembly);
    var viewModel = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Custom.ViewModel");
    var controls = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Custom.Controls");
    Verifier.VerifyAccessType(controls, viewModel, AbstractionType.Interface);
}

Are all members used

The verifier checks for all members that are covered by the passed descriptor whether there are referenced to them, in this case all members of the module Printer. We explicitly exclude generated code, for example private members introduced by XAML. Also overrides and implementations of interfaces are excluded. So currently it forces you to call a method on the most abstract type. How to determine whether a member is implementing an interface? The type System.Reflections.MethodBase has a property “Attributes”. It describes things like visibility and inheritance. When you find a method is marked Final, Virtual, VtableLayoutMask then it implements an interface method. Overrides hvae the Virtual attribute and must not have the VtableLayoutMask attribute, which is applied to the corresponding method of the first declaring type. When you find Private, Final and Virtual it is a hint for generated code – strange combination. At least this seems to work in most cases. Classes and Members marked with the GeneratedCodeAttribute are marked as generated too.

[TestMethod]
public void PrinterAllPrivateMembersAreUsed()
{
Verifier.VerifyMembersAreUsed(DescriptorBuilder.BuildAssemblyDescriptor(
typeof(Printer.Properties.IAssemblyIdentifier).Assembly), NodeVisibility.Private);
}

Versatility of Types

VerifyVersatility checks whether the types specified or included in the specified namespace or module are used by at least two other types in the project. This is not required for all of your types, but when you for example create DTOs for internal use or state objects, they tend to be obsolete when being used by only a single type.

        [TestMethod]
        public void DataModelTypesAreUsedByMoreThanOneType()
        {
            var moduleDescriptor = DescriptorBuilder.BuildAssemblyDescriptor(typeof(Communication.Properties.IAssemblyIdentifier).Assembly);
            var model = DescriptorBuilder.BuildNamespaceDescriptor(moduleDescriptor, "Global.DataModel");
            var allowedExceptions = new List<IDescriptor>
                    {
                        DescriptorBuilder.BuildAssemblyDescriptor(typeof(Global.DataModel.DataObjectConverter))
                    };
            Verifier.VerifyVersatility(model, allowedExceptions);
        }

Custom rules

Although unit tests seldom go conform with the Arrange-Act-Assert rule, I find the AssertThat of NUnit quite comfortable. When you want to check higher level metrics or the combination of multiple values then it can help to make the test more readable. Therefore, I have added a method VerifyThat to the Verifier class.

The method is executed for any object that matches the generic type T and is part of the dependency model. As parameter you pass an assumption, which is checked for all matching objects. When the assumption does not hold for one object then it is added to the list of violations, which are reported inside an exception in case there are any violations.

As example I present you a house rule, which says that any type that refers to at least eight other user types is considered an octopus. It means the afferent coupling to types of the same project is high. It is derived from a rule people use for organizing everyday live.

  • An average human being can keep up to seven elements in short term memory. 
  • A company is structured into units, departments, teams of usually a limited number of elements. When the number exceeds 7 split into smaller groups.
    Slides of good presentations limit the number of informational units to 7 or below.
  • Technical relief units segregate the area of operations in 5 to 7 subareas.
  • Aborigines have a word for the number 1, 2, 3, 4, 5, 6, 7. Anything more is “many”.
    there are more examples

So an octopus is hard to grasp, an octopus has at least one arm more than you would feel comfortable with.

 

[TestMethod]
public void VerifyNoOctopus()
{
    var dependenciesPerTypeOut = Model.DependenciesPerTypeOut;
    Verifier.VerifyThat<TypeDescriptor>((x, state) =>
    {
var types = VerifierUtilities.AggregateDependenciesToTypes(dependenciesPerTypeOut[x], false); int count = types.Count(y => Filters.MyTypes(y) && y.AbstractionType == AbstractionType.Concrete); return count < 8; }, Filters.MyTypes); }

Example projects

For exmaple projects, check out the latest source code and look at the Examples solution folder.

Egg and Gherkin is a development tool written in C# to qualify the evolvement of your software architecture within predefined limits. Controlled by the unit test framework of your choice, it gives immediate feedback when breaking architectural constraints.

Last edited Oct 4, 2015 at 6:06 PM by The_eg, version 11

Comments

No comments yet.