Click here to Skip to main content
15,886,036 members
Articles / Programming Languages / C#

Advanced Observer Design Pattern via Events – Design Patterns Automation Testing

Rate me:
Please Sign up or sign in to vote.
4.93/5 (11 votes)
5 Jul 2015Ms-PL5 min read 14.1K   22  
Create an extendable test execution in automation tests via Observer Design Pattern. Explains an implementation in C# via events and delegates.

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.

Image 1

UML Class Diagram

Image 2

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.

Image 3

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.

C#
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.

C#
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.

C#
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.

Image 4

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.

C#
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.

Image 5

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.

C#
[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.

C#
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.

Image 6

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.

C#
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

  1. Page Object Pattern
  2. Advanced Page Object Pattern
  3. Facade Design Pattern
  4. Singleton Design Pattern
  5. Fluent Page Object Pattern
  6. IoC Container and Page Objects
  7. Strategy Design Pattern
  8. Advanced Strategy Design Pattern
  9. Observer Design Pattern
  10. Observer Design Pattern via Events and Delegates
  11. Observer Design Pattern via IObservable and IObserver
  12. Decorator Design Pattern- Mixing Strategies
  13. Page Objects That Make Code More Maintainable
  14. Improved Facade Design Pattern in Automation Testing v.2.0
  15. Rules Design Pattern
  16. Specification Design Pattern
  17. 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

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
CEO Automate The Planet
Bulgaria Bulgaria
CTO and Co-founder of Automate The Planet Ltd, inventor of BELLATRIX Test Automation Framework, author of "Design Patterns for High-Quality Automated Tests: High-Quality Test Attributes and Best Practices" in C# and Java. Nowadays, he leads a team of passionate engineers helping companies succeed with their test automation. Additionally, he consults companies and leads automated testing trainings, writes books, and gives conference talks. You can find him on LinkedIn every day.

Comments and Discussions

 
-- There are no messages in this forum --