Introduction
In my articles from the series “Design Patterns in Automation Testing“, I am sharing with you ideas how to integrate the most useful code design patterns in the automation testing. In my last publication, I explained to you how to create an extendable test execution engine for Web Driver utilizing the classical implementation of the Observer Design Pattern. Here, I am going to show you another more advanced implementation of the Observer Design Pattern using the power of the .NET’s events and delegates.
UML Class Diagram
Participants
The classes and objects participating in this pattern are:
ITestExecutionProvider
– Objects use this interface to register as observers and also to remove themselves from being observers. MSTestExecutionProvider
– The concrete provider/subject always implements IProvider
interface. The particular provider holds different notification methods that are used to update all of the subscribed observers whenever the state changes. TestExecutionEventsArgs
– The object that is used to transfer the state of the provider to the concrete observers. BaseTestBehaviorObserver
– All potential observers need to inherit the base observer class. Additionally to Subscribe
and Unsubscribe
methods, it holds empty methods that can be later overridden by the concrete observers. OwnerTestBehaviorObserver
– A concrete observer can be any class that implements the BaseObserver class. Each observer registers with a particular provider to receive updates via subscribing to the provider’s events. BaseTest
– The parent class for all test classes in the framework. Uses the TestExecutionProvider
to extends its test execution capabilities via test/class level defined attributes and concrete observers.
Observer Design Pattern C# Code
Use Case
The primary goal of the sample code is to provide an easy way to automation engineers to add additional logic to the current test execution via class/test level attributes. For example, configure the current test execution browser or fail the test if the owner attribute is not set.
The following class structure is going to be used.
You can find more information about the classical implementation of the Observer Design Pattern in my previous article- “Observer Design Pattern- Design Patterns Automation Testing“. If you compare the UML diagrams of the classical implementation and the events based one, you will probably notice that they are almost identical. One of the changes is the absence of the ITestBehaviorObserver
interface. Nearly all of the other changes are located in the provider or as previously named subject class. (The two names- subject and provider are possible) The provider’s interface now contains only events that are used by the concrete observers to subscribe to various update points. Also, the provider doesn’t hold any Subscribe
and Unsubscribe
methods. Instead, events are used.
Now the interface for the provider class in this implementation of the Observer Design Pattern looks like the code below.
public interface IExecutionProvider
{
event EventHandler<TestExecutionEventArgs> TestInstantiatedEvent;
event EventHandler<TestExecutionEventArgs> PreTestInitEvent;
event EventHandler<TestExecutionEventArgs> PostTestInitEvent;
event EventHandler<TestExecutionEventArgs> PreTestCleanupEvent;
event EventHandler<TestExecutionEventArgs> PostTestCleanupEvent;
}
The concrete provider looks almost identical to the previously developed with minor changes.
public class MSTestExecutionProvider : IExecutionProvider
{
public event EventHandler<TestExecutionEventArgs> TestInstantiatedEvent;
public event EventHandler<TestExecutionEventArgs> PreTestInitEvent;
public event EventHandler<TestExecutionEventArgs> PostTestInitEvent;
public event EventHandler<TestExecutionEventArgs> PreTestCleanupEvent;
public event EventHandler<TestExecutionEventArgs> PostTestCleanupEvent;
public void PreTestInit(TestContext context, MemberInfo memberInfo)
{
this.RaiseTestEvent(this.PreTestInitEvent, context, memberInfo);
}
public void PostTestInit(TestContext context, MemberInfo memberInfo)
{
this.RaiseTestEvent(this.PostTestInitEvent, context, memberInfo);
}
public void PreTestCleanup(TestContext context, MemberInfo memberInfo)
{
this.RaiseTestEvent(this.PreTestCleanupEvent, context, memberInfo);
}
public void PostTestCleanup(TestContext context, MemberInfo memberInfo)
{
this.RaiseTestEvent(this.PostTestCleanupEvent, context, memberInfo);
}
public void TestInstantiated(MemberInfo memberInfo)
{
this.RaiseTestEvent(this.TestInstantiatedEvent, null, memberInfo);
}
private void RaiseTestEvent(EventHandler<TestExecutionEventArgs> eventHandler,
TestContext testContext, MemberInfo memberInfo)
{
if (eventHandler != null)
{
eventHandler(this, new TestExecutionEventArgs(testContext, memberInfo));
}
}
}
In the different test execution points, the method RaiseTestEvent
is used to notify all subscribed observers for that particular execution point. If there are not any subscribers, the event is not triggered. The concrete observer’s needed information is passed by the creation of a new object of the type TestExecutionEventArgs
.
public class TestExecutionEventArgs : EventArgs
{
private readonly TestContext testContext;
private readonly MemberInfo memberInfo;
public TestExecutionEventArgs(TestContext context, MemberInfo memberInfo)
{
this.testContext = context;
this.memberInfo = memberInfo;
}
public MemberInfo MemberInfo
{
get
{
return this.memberInfo;
}
}
public TestContext TestContext
{
get
{
return this.testContext;
}
}
}
It only contains two properties. The MSTest TestContext
and the MemberInfo
which is the reflection information about the currently executing test method.
Create Base Observer Using .NET Event and Delegates
As I have already pointed in the Events
based implementation of the Observer Design Pattern, the base observer class doesn’t need to implement any interfaces.
public class BaseTestBehaviorObserver
{
public void Subscribe(IExecutionProvider provider)
{
provider.TestInstantiatedEvent += this.TestInstantiated;
provider.PreTestInitEvent += this.PreTestInit;
provider.PostTestInitEvent += this.PostTestInit;
provider.PreTestCleanupEvent += this.PreTestCleanup;
provider.PostTestCleanupEvent += this.PostTestCleanup;
}
public void Unsubscribe(IExecutionProvider provider)
{
provider.TestInstantiatedEvent -= this.TestInstantiated;
provider.PreTestInitEvent -= this.PreTestInit;
provider.PostTestInitEvent -= this.PostTestInit;
provider.PreTestCleanupEvent -= this.PreTestCleanup;
provider.PostTestCleanupEvent -= this.PostTestCleanup;
}
protected virtual void TestInstantiated(object sender, TestExecutionEventArgs e)
{
}
protected virtual void PreTestInit(object sender, TestExecutionEventArgs e)
{
}
protected virtual void PostTestInit(object sender, TestExecutionEventArgs e)
{
}
protected virtual void PreTestCleanup(object sender, TestExecutionEventArgs e)
{
}
protected virtual void PostTestCleanup(object sender, TestExecutionEventArgs e)
{
}
}
In the Subscribe
method, the concrete observer is subscribed to all available provider’s events. However, the wired methods are empty. This gives the specific child observer the flexibility to override only the needed methods. These parent methods are marked as protected
so they cannot be put in an interface.
By the way, during my research for the “Design Patterns in Automation Testing” series, I always first read about the presented pattern in several books. One of them that you might want to check is “Head First Design Patterns” by Eric Freeman. The author uses an unique methodology for presenting the material that I haven’t found anywhere else. Probably, most of you will like it. For the more hardcore fans that might find the book too easy, I recommend the bible of the design patterns- “Design Patterns- Elements of Reusable Object-Oriented Software”. It will change your way of thinking about object-oriented design.
Create Concrete Observers Powered by Attributes
The primary goal was to create a way so that the user to be able to control the current test’s execution browser type through attributes. Below, you can see that the usage of the BaseTest
class and the ExecutionBrowser
attribute didn’t change.
[TestClass]
[ExecutionBrowser(BrowserTypes.Chrome)]
public class BingTestsDotNetEvents : BaseTest
{
[TestMethod]
[ExecutionBrowser(BrowserTypes.Firefox)]
public void SearchTextInBing_First_Observer()
{
B.BingMainPage bingMainPage = new B.BingMainPage(Driver.Browser);
bingMainPage.Navigate();
bingMainPage.Search("Automate The Planet");
bingMainPage.ValidateResultsCount("RESULTS");
}
}
The test execution flow stays intact. The only change in the concrete observers is that the overridden method should be marked as protected
instead of as public
.
public class BrowserLaunchTestBehaviorObserver : BaseTestBehaviorObserver
{
protected override void PreTestInit(object sender, TestExecutionEventArgs e)
{
var browserType = this.GetExecutionBrowser(e.MemberInfo);
Driver.StartBrowser(browserType);
}
protected override void PostTestCleanup(object sender, TestExecutionEventArgs e)
{
Driver.StopBrowser();
}
private BrowserTypes GetExecutionBrowser(MemberInfo memberInfo)
{
BrowserTypes result = BrowserTypes.Firefox;
BrowserTypes classBrowserType = this.GetExecutionBrowserClassLevel(memberInfo.DeclaringType);
BrowserTypes methodBrowserType = this.GetExecutionBrowserMethodLevel(memberInfo);
if (methodBrowserType != BrowserTypes.NotSet)
{
result = methodBrowserType;
}
else if (classBrowserType != BrowserTypes.NotSet)
{
result = classBrowserType;
}
return result;
}
private BrowserTypes GetExecutionBrowserMethodLevel(MemberInfo memberInfo)
{
var executionBrowserAttribute = memberInfo.GetCustomAttribute<ExecutionBrowserAttribute>(true);
if (executionBrowserAttribute != null)
{
return executionBrowserAttribute.BrowserType;
}
return BrowserTypes.NotSet;
}
private BrowserTypes GetExecutionBrowserClassLevel(Type type)
{
var executionBrowserAttribute = type.GetCustomAttribute<ExecutionBrowserAttribute>(true);
if (executionBrowserAttribute != null)
{
return executionBrowserAttribute.BrowserType;
}
return BrowserTypes.NotSet;
}
}
The code for controlling the browser type is almost identical with only the previously mentioned difference.
Putting All Together in BaseTest Class
Through the usage of separate classes for the implementation of the pattern, there are almost no changes in the BaseTest
class. Only the implementations of the concrete provider and observers are replaced.
public class BaseTest
{
private readonly MSTestExecutionProvider currentTestExecutionProvider;
private TestContext testContextInstance;
public BaseTest()
{
this.currentTestExecutionProvider = new MSTestExecutionProvider();
this.InitializeTestExecutionBehaviorObservers(this.currentTestExecutionProvider);
var memberInfo = MethodInfo.GetCurrentMethod();
this.currentTestExecutionProvider.TestInstantiated(memberInfo);
}
public string BaseUrl { get; set; }
public IWebDriver Browser { get; set; }
public TestContext TestContext
{
get
{
return testContextInstance;
}
set
{
testContextInstance = value;
}
}
public string TestName
{
get
{
return this.TestContext.TestName;
}
}
[TestInitialize]
public void CoreTestInit()
{
var memberInfo = GetCurrentExecutionMethodInfo();
this.currentTestExecutionProvider.PreTestInit(this.TestContext, memberInfo);
this.TestInit();
this.currentTestExecutionProvider.PostTestInit(this.TestContext, memberInfo);
}
[TestCleanup]
public void CoreTestCleanup()
{
var memberInfo = GetCurrentExecutionMethodInfo();
this.currentTestExecutionProvider.PreTestCleanup(this.TestContext, memberInfo);
this.TestCleanup();
this.currentTestExecutionProvider.PostTestCleanup(this.TestContext, memberInfo);
}
public virtual void TestInit()
{
}
public virtual void TestCleanup()
{
}
private MethodInfo GetCurrentExecutionMethodInfo()
{
var memberInfo = this.GetType().GetMethod(this.TestContext.TestName);
return memberInfo;
}
private void InitializeTestExecutionBehaviorObservers
(MSTestExecutionProvider currentTestExecutionProvider)
{
new AssociatedBugTestBehaviorObserver().Subscribe(currentTestExecutionProvider);
new BrowserLaunchTestBehaviorObserver().Subscribe(currentTestExecutionProvider);
new OwnerTestBehaviorObserver().Subscribe(currentTestExecutionProvider);
}
}
So Far in the "Design Patterns in Automated Testing" Series
- Page Object Pattern
- Advanced Page Object Pattern
- Facade Design Pattern
- Singleton Design Pattern
- Fluent Page Object Pattern
- IoC Container and Page Objects
- Strategy Design Pattern
- Advanced Strategy Design Pattern
- Observer Design Pattern
- Observer Design Pattern via Events and Delegates
- Observer Design Pattern via IObservable and IObserver
- Decorator Design Pattern- Mixing Strategies
- Page Objects That Make Code More Maintainable
- Improved Facade Design Pattern in Automation Testing v.2.0
- Rules Design Pattern
- Specification Design Pattern
- Advanced Specification Design Pattern
If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!
Source Code
References
The post- Advanced Observer Design Pattern via Events – Design Patterns Automation Testing appeared first on Automate The Planet.
All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement