The previous chapter shows the requirements of the Test Suite product. Some of them can be fulfilled by a modular application design. Fowler writes that modularity is about hiding a secret in its implementation that is not apparent from the interface [Fowler04]. An example can be seen in Figure 1 whereas the modules hide their implementation behind an interface.
Figure 1: The client uses one of the modules through an interface.
A modular application design has two essential advantages. The first one is that the developer who writes the client does not have to understand the implementation of the module to use it. The second advantage is that the implementation can be exchanged without rewriting and recompiling the client code. To prevent recompiling, a binary compatibility is necessary between the client and the module. The .NET Framework provides this by compiling the source code (e.g. C#) into Intermediate Language (IL) code. The IL code contains tokens to identify the fields and methods instead of using offset memory addresses as it is common in machine code. This is possible because the IL contains the meta-data of all types. Before the IL code can be run, it must be converted by the Just-In-Time (JIT) compiler to native machine code. The JIT compiling is an automatic process which is started at runtime by the .NET Framework.
Separation of interface and implementation, and binary compatibility are a subset of the principles of component-oriented programming [Löwy05, p. 6].
Over the last decade, component-oriented programming has established itself as the predominant software development methodology. (…) Practitioners have discovered that by breaking down a system into binary components, they can attain much greater reusability, extensibility, and maintainability [Löwy05, p. 1].
Popular component-enabling technologies are DCOM, CORBA, JavaBeans, and the .NET Framework which is a relatively new member in this field. As Löwy writes the other principles of component-oriented programming are language independence, location transparency, concurrency management, version control, and component-based security. These principles are all supported by the .NET Framework whereas a programmer does not have to adopt all of them.
A component-enabling technology like the .NET Framework already fulfills some of the Test Suite requirements. But one important aspect is not handled by component-oriented programming. Someone has to wire the client with the appropriate module implementation together. In the example seen in Figure 1 the client could use the implementation of module A or B. One of the Test Suite requirements needs the wiring dependent on the runtime configuration. For example the client uses the implementation of module A by default and during testing it uses the mockup implementation of module B. How this can be accomplished in a flexible way is described as Plugin pattern [Fowler03, p. 499].
Use Plugin whenever you have behaviors that require different implementations based on runtime environment. [Fowler03, p. 500]
The Plugin pattern adapts the Factory pattern [Larman04, p. 440] which reads the linking (wiring) instructions from a single, external point in order to keep the configuration management easy. The linking has to be done at runtime rather than during compile time. Otherwise, a rebuild would be necessary if the configuration changes. This can be accomplished via reflection which is supported by the .NET Framework.
A software design that uses the Plugin pattern is called plug-in architecture in this thesis. These architectures can be divided into two categories which are based on the same concept but fulfill different requirements. The former one is used to increase the modularity of the internal application design. This one is addressed in this diploma thesis. The latter plug-in architecture is used to provide an automation and extension interface for third parties. It shares the requirements of the former one and comes up with new ones. The most important additional requirements are backward compatibility and isolation. An application (host) evolves considerably faster as the plug-ins of third parties. Thus, the interfaces for external plug-ins have to be more robust as the internal application design. Otherwise it is likely that plug-ins stop working because the application was updated. Some plug-in frameworks help to ensure backward compatibility for older plug-ins. The second requirement is the isolation of a plug-in from the host and other plug-ins. This allows the host to be unaffected of unstable plug-ins. In addition, it is often combined with sandboxing of the plug-ins to increase the security. The realization of these advanced requirements comes at high costs. A version resilient architecture is far more complex and the communication between isolated parts comes with high performance penalties. Although the Test Suite does also require this kind of plug-in architecture, it is not in the scope of this diploma thesis.
Modularization is an important principle in software engineering. It improves the reusability which can lead to faster time to market, and lower development and long-term maintenance costs. Modular software design is promoted by evolving development methodologies like procedural programming, object-orientation, component-orientation and aspect-orientation.
(…), separating the interface from the implementation and separating configuration from use are two vital principles in a good modularization scheme. [Fowler04, p. 67]