Click here to Skip to main content
15,881,938 members
Articles / Programming Languages / C#

Advanced Observer Design Pattern via IObservable and IObserver in Automation Testing

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
12 Jul 2015Ms-PL6 min read 10.4K   16  
Create an extendable test execution in automation tests via Observer Design Pattern. Explains an implementation in C# via .NET IObserver interfaces.

Introduction

The “Design Patterns in Automation Testing” series are all about the integration of the most practical design patterns in the automation testing. Last two articles from the sequence were dedicated to the Observer Design Pattern. The first one was about the classic implementation of the pattern and the second one about its events-delegates substitute. There is a third possible implementation of the observer design pattern that is a new one for .NET. It uses the IObserver and IObservable interfaces. This last article dedicated to the observer design pattern is going to look into this new implementation.

Image 1

UML Class Diagram

Image 2

Participants

The classes and objects participating in this pattern are:

  • ITestExecutionProvider – Contains primary method definitions that every execution provider should implement.
  • MSTestExecutionProvider – The concrete provider/subject always implements the IProvider interface. The particular provider holds different notification methods that are used to update all of the subscribed observers whenever the state changes.
  • ExecutionStatus – 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 the Subscribe and Unsubscribe methods, it holds empty methods that can be later overridden by the concrete observers.
  • OwnerTestBehaviorObserver – A particular observer can be any class that implements the BaseObserver class. Each observer registers with a particular provider to receive updates via subscribing to the supplier’s events.
  • BaseTest – The parent class for all test classes in the framework. Uses the TestExecutionProvider to extend its test execution capabilities via test/class level defined attributes and concrete observers.
  • Unsubscriber – Object that is used to healthy dispose observers when they go out of scope.

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

One of the differences between the classic implementation and the IObservable one is that the TestExecutionProvider doesn’t hold Unsubscribe/Detach method. Next, there is a big difference in the implementation of the Subscribe method. The new implementation uses a new class called Unsubscriber whose task is to healthy release any Observer that goes out of scope.

C#
public class MSTestExecutionProvider : IObservable<ExecutionStatus>, 
	IDisposable, ITestExecutionProvider
{
    private readonly List<IObserver<ExecutionStatus>> testBehaviorObservers;

    public MSTestExecutionProvider()
    {
        this.testBehaviorObservers = new List<IObserver<ExecutionStatus>>();
    }

    public void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
        this.NotifyObserversExecutionPhase(context, memberInfo, ExecutionPhases.PreTestInit);
    }

    public void PostTestInit(TestContext context, MemberInfo memberInfo)
    {
        this.NotifyObserversExecutionPhase(context, memberInfo, ExecutionPhases.PostTestInit);
    }

    public void PreTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        this.NotifyObserversExecutionPhase(context, memberInfo, ExecutionPhases.PreTestCleanup);
    }

    public void PostTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        this.NotifyObserversExecutionPhase(context, memberInfo, ExecutionPhases.PostTestCleanup);
    }

    public void TestInstantiated(MemberInfo memberInfo)
    {
        this.NotifyObserversExecutionPhase(null, memberInfo, ExecutionPhases.TestInstantiated);
    }

    public IDisposable Subscribe(IObserver<ExecutionStatus> observer)
    {
        if (!testBehaviorObservers.Contains(observer))
        {
            testBehaviorObservers.Add(observer);
        }
        return new Unsubscriber<ExecutionStatus>(testBehaviorObservers, observer);
    }

    private void NotifyObserversExecutionPhase
    	(TestContext context, MemberInfo memberInfo, ExecutionPhases executionPhase)
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.OnNext(new ExecutionStatus(context, memberInfo, executionPhase));
        }
    }

    public void Dispose()
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.OnCompleted();
        }

        this.testBehaviorObservers.Clear();
    }
}

The Unsubscriber is a generic class that implements the IDisposable interface. It takes care of the removal process of the concrete observer from the provider’s collection. In the other implementations of the Observer Design Pattern, when an observer goes out of scope, you might think that its memory will be cleaned by the Garbage Collector. However, if its reference held in the provider is not properly released, the GC will wait until the supplier goes out a scope to clean the observer’s memory. The implementation of the IObserver and IObservable interfaces prevents it.

C#
internal class Unsubscriber<T> : IDisposable
{
    private List<IObserver<T>> observers;
    private IObserver<T> observer;

    internal Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer)
    {
        this.observers = observers;
        this.observer = observer;
    }

    public void Dispose()
    {
        if (observers.Contains(observer))
            observers.Remove(observer);
    }
}

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 a very 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 4

Notification Methods Exposed by IObserver Interface

The another big difference in this implementation is that the observers interface IObserver presents only three public methods to the concrete provider- OnNext, OnError, and OnCompleted.

  • OnNext – The provider calls this method to send notifications to the subscribed observers.
  • OnError – This method notifies the observers that an error happened in the reported data. Not necessary means that an exception occurred. It should be seen as informational, the provider should not expect the observers to provide an error handling.
  • OnCompleted – Reports that no further data is available.

To be able to support multiple notification points with different behavior, we use the ExecutionPhases enumeration to notify the particular observers for the current execution status. This enum’s reference is held in the object that is used to pass notification data between the provider and its observers - ExecutionStatus.

C#
public class ExecutionStatus
{
    public TestContext TestContext { get; set; }

    public MemberInfo MemberInfo { get; set; }
        
    public ExecutionPhases ExecutionPhase { get; set; }

    public ExecutionStatus(TestContext testContext, 
    	ExecutionPhases executionPhase) : this(testContext, null, executionPhase)
    {
    }

    public ExecutionStatus
    (TestContext testContext, MemberInfo memberInfo, ExecutionPhases executionPhase)
    {
        this.TestContext = testContext;
        this.MemberInfo = memberInfo;
        this.ExecutionPhase = executionPhase;
    }
}

It holds three properties- the MSTest TestContext object, reflection MemberInfo object containing an information about the currently executed test method and the current execution phase.

There are five possible execution phases:

C#
public enum ExecutionPhases
{
    TestInstantiated,
    PreTestInit,
    PostTestInit,
    PreTestCleanup,
    PostTestCleanup
}

The provider calls the OnNext method of each subscribed observer in the specific notification points, passing a new instance of the ExecutionStatus data object.

C#
private void NotifyObserversExecutionPhase
	(TestContext context, MemberInfo memberInfo, ExecutionPhases executionPhase)
{
    foreach (var currentObserver in this.testBehaviorObservers)
    {
        currentObserver.OnNext(new ExecutionStatus(context, memberInfo, executionPhase));
    }
}

Image 5

Create Base Observer Using IObserver Interface

The base observer class implements the IObserver<ExecutionStatus> interface. It requires the implementation of the previously mentioned three methods - OnNext, OnError, and OnCompleted. However, the class holds more important methods like Subscribe and Unsubscribe. Also, it exposes empty protected virtual methods to its children for the different notification points like PostTestCleanup and PreTestInit.

C#
public class BaseTestBehaviorObserver : IObserver<ExecutionStatus>
{
    private IDisposable cancellation;

    public virtual void Subscribe(IObservable<ExecutionStatus> provider)
    {
        cancellation = provider.Subscribe(this);
    }

    public virtual void Unsubscribe()
    {
        cancellation.Dispose();
    }

    public void OnNext(ExecutionStatus currentExecutionStatus)
    {
        switch (currentExecutionStatus.ExecutionPhase)
        {
            case ExecutionPhases.TestInstantiated:
                this.TestInstantiated(currentExecutionStatus.MemberInfo);
                break;
            case ExecutionPhases.PreTestInit:
                this.PreTestInit
                (currentExecutionStatus.TestContext, currentExecutionStatus.MemberInfo);
                break;
            case ExecutionPhases.PostTestInit:
                this.PostTestInit
                (currentExecutionStatus.TestContext, currentExecutionStatus.MemberInfo);
                break;
            case ExecutionPhases.PreTestCleanup:
                this.PreTestCleanup
                (currentExecutionStatus.TestContext, currentExecutionStatus.MemberInfo);
                break;
            case ExecutionPhases.PostTestCleanup:
                this.PostTestCleanup
                (currentExecutionStatus.TestContext, currentExecutionStatus.MemberInfo);
                break;
            default:
                break;
        }
    }

    public virtual void OnError(Exception e)
    {
        Console.WriteLine("The following exception occurred: {0}", e.Message);
    }

    public virtual void OnCompleted()
    {
    }

    protected virtual void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
    }

    protected virtual void PostTestInit(TestContext context, MemberInfo memberInfo)
    {
    }

    protected virtual void PreTestCleanup(TestContext context, MemberInfo memberInfo)
    {
    }

    protected virtual void PostTestCleanup(TestContext context, MemberInfo memberInfo)
    {
    }

    protected virtual void TestInstantiated(MemberInfo memberInfo)
    {
    }
}

The Subscribe method accepts a provider instance as a parameter and calls its Subscribe implementation to attach itself. The Unsubscribe method only calls the Unsubscriber’s Dispose method where the observer is removed from the provider’s observers collection.

The base OnNext method performs different actions based on the current execution phase. The default behavior is that if the child behaviors don’t override the empty implementations of the pre and post methods, nothing will happen. With this mechanism in hand, the concrete observers can implement only the needed notification point methods, not all of them.

C#
public class OwnerTestBehaviorObserver : BaseTestBehaviorObserver
{
    protected override void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
        this.ThrowExceptionIfOwnerAttributeNotSet(memberInfo);
    }

    private void ThrowExceptionIfOwnerAttributeNotSet(MemberInfo memberInfo)
    {
        try
        {
            memberInfo.GetCustomAttribute<OwnerAttribute>(true);
        }
        catch
        {
            throw new Exception("You have to set Owner of your test before you run it");
        }
    }
}

The only difference compared to the other implementations of the Observer Design Pattern in the concrete observer is that the Pre and Post methods are marked as protected.

Image 6

Assemble Everything 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. Although it is possible to attach an observer to multiple providers, the recommended pattern is to connect an IObserver<T> instance to only one IObservable<T> instance.

C#
[TestClass]
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;
        }
    }

    [ClassInitialize]
    public static void OnClassInitialize(TestContext context)
    {
    }

    [ClassCleanup]
    public static void OnClassCleanup()
    {
    }

    [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 IObservable and IObserver in 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 --