Click here to Skip to main content
13,894,940 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

12.8K views
17 bookmarked
Posted 1 Jul 2015
Licenced Ms-PL

Observer Design Pattern- Design Patterns Automated Testing

, 1 Jul 2015
Rate this:
Please Sign up or sign in to vote.
A detailed overview with examples how to utilize the Observer Design Pattern in automated tests to create an extendable test execution.

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. This type of integration brings more maintainable and extendable code through the following of the SOLID principles. Most of the techniques follow the Open Close Principle where the code should be open for extension but closed for modification. I am going to use this principle in the current publication heavily. I am going to explain to you how to create an extendable test execution engine for Web Driver utilizing the classical implementation of the Observer Design Pattern. I am going to present to you even better implementations using .NET built-in event\delegates and one with IObserver<T>. You will be able to find them in the next Advanced Observer Design Pattern article.

Definition

The Observer Design Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.

  • Strive for loosely coupled designs between objects that interact.
  • Allows you to send data to many other objects in a very efficient manner.
  • No modification is needed to be done to the subject to add new observers.
  • You can add and remove observers at any time.
  • The order of Observer notifications is undependable.

UML Class Diagram

Participants

The classes and objects participating in this pattern are:

  • ITestExecutionSubject– Objects use this interface to register as observers and also to remove themselves from being observers.
  • MSTestExecutionSubject– The concrete subject always implements the ISubject interface. In addition to the attach and detach methods, the specific subject implements different notification methods that are used to update all of the subscribed observers whenever the state changes.
  • ITestBehaviorObserver– All potential observers need to implement the observer interface. These methods are called at the different points when the subject’s state changes.
  • OwnerTestBehaviorObserver– A concrete observer can be any class that implements IObserver interface. Each observer registers with a specific subject to receiving updates.
  • BaseTest– The parent class for all test classes in the framework. Uses the TestExecutionSubject 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 current test execution browser or fail the test if the owner attribute is not set.

The following class structure is going to be used.

With the Observer Design Pattern, the Subject is the object that contains the state and controls it. So, there is one subject with a state. The observers, on the other hand, use the state, even if they don’t own it. There are many observers, and they rely on the Subject to tell them when its state changes. So there is a relationship between the one Subject to the many Observers.

The first step to the integration of the Observer Design Pattern in the automation test framework is to create the ISubject interface.

public interface ITestExecutionSubject
{
    void Attach(ITestBehaviorObserver observer);

    void Detach(ITestBehaviorObserver observer);

    void PreTestInit(TestContext context, MemberInfo memberInfo);

    void PostTestInit(TestContext context, MemberInfo memberInfo);

    void PreTestCleanup(TestContext context, MemberInfo memberInfo);
        
    void PostTestCleanup(TestContext context, MemberInfo memberInfo);

    void TestInstantiated(MemberInfo memberInfo);
}

The first two methods Attach and Detach are used by the observer classes to associate themselves with the subject. The rest of the interface’s methods are called in the different steps of the test execution workflow to notify the observers about the changes in the state of the subject.

In order the subject to be able to notify the observers, they need to implement the IObserver interface where all notification methods are defined.

public interface ITestBehaviorObserver
{
    void PreTestInit(TestContext context, MemberInfo memberInfo);

    void PostTestInit(TestContext context, MemberInfo memberInfo);

    void PreTestCleanup(TestContext context, MemberInfo memberInfo);

    void PostTestCleanup(TestContext context, MemberInfo memberInfo);

    void TestInstantiated(MemberInfo memberInfo);
}

After that, a concrete subject class should be created.

public class MSTestExecutionSubject : ITestExecutionSubject
{
    private readonly List<ITestBehaviorObserver> testBehaviorObservers;

    public MSTestExecutionSubject()
    {
        this.testBehaviorObservers = new List<ITestBehaviorObserver>();
    }

    public void Attach(ITestBehaviorObserver observer)
    {
        testBehaviorObservers.Add(observer);
    }

    public void Detach(ITestBehaviorObserver observer)
    {
        testBehaviorObservers.Remove(observer);
    }

    public void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.PreTestInit(context, memberInfo);
        }
    }
    public void PostTestInit(TestContext context, MemberInfo memberInfo)
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.PostTestInit(context, memberInfo);
        }
    }

    public void PreTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.PreTestCleanup(context, memberInfo);
        }
    }

    public void PostTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.PostTestCleanup(context, memberInfo);
        }
    }

    public void TestInstantiated(MemberInfo memberInfo)
    {
        foreach (var currentObserver in this.testBehaviorObservers)
        {
            currentObserver.TestInstantiated(memberInfo);
        }
    }
}

The specific subject knows nothing about the particular implementations of the observers. He is working with a list of ITestBehaviorObserver. The Attach and Detach methods add and remove observers to/from the collection. In this classic implementation of the observer design pattern, the observers are responsible to associate themselves with the subject class.

For the current use case, not all of the observers need to implement all of the notification methods. For example, the observer for the Owner attribute needs to execute code only for PostTestCleanup. In order to support this requirement, we are going to add a base class that is going to implement the ITestBehaviorObserver interface.

public class BaseTestBehaviorObserver : ITestBehaviorObserver
{
    private readonly ITestExecutionSubject testExecutionSubject;

    public BaseTestBehaviorObserver(ITestExecutionSubject testExecutionSubject)
    {
        this.testExecutionSubject = testExecutionSubject;
        testExecutionSubject.Attach(this);
    }

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

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

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

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

    public virtual void TestInstantiated(MemberInfo memberInfo)
    {
    }
}

As all notification methods are empty, the child class needs only to override the necessary ones. Also, the base class constructor requires a ITestExecutionSubject parameter in order to be able to associate the current observer to the subject.

Configure Test Execution Browser with Attribute

Now it is time to utilize all these classes to solve some practical problems. The primary goal is to create a way so that the user to be able to control the current test’s execution browser type through attributes.

[TestClass]
[ExecutionBrowser(BrowserType = BrowserTypes.Firefox)]
public class BingTestsClassicObserver : BaseTest
{
    [TestMethod]
    [ExecutionBrowser(BrowserType = BrowserTypes.Chrome)]
    public void SearchTextInBing_First_Observer()
    {
        B.BingMainPage bingMainPage = new B.BingMainPage(Driver.Browser);
        bingMainPage.Navigate();
        bingMainPage.Search("Automate The Planet");
        bingMainPage.ValidateResultsCount("RESULTS");
    }
}

In the above example, the new attribute is used to configure the test engine to use the Chrome browser for all tests in the class. However, the test level attribute is going to override the class level one. So for the SearchTextInBing_First_Observer test the Web Driver browser is going to be set to Firefox.

There is nothing special about the ExecutionBrowserAttribute, it only holds a property of the enum BrowserTypes.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class ExecutionBrowserAttribute : Attribute
{
    public ExecutionBrowserAttribute(BrowserTypes browser)
    {
        this.BrowserType = browser;
    }

    public BrowserTypes BrowserType { get; set; }
}

This attribute is configured to be available on test and class level through the AttributeUsage attribute.

The logic that extracts the current values of the new attribute and the configuration of the Web Driver engine is implemented in a new concrete TestBehaviorObserver.

public class BrowserLaunchTestBehaviorObserver : BaseTestBehaviorObserver
{
    public BrowserLaunchTestBehaviorObserver(ITestExecutionSubject testExecutionSubject)
        : base(testExecutionSubject)
    {
    }

    public override void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
        var browserType = this.GetExecutionBrowser(memberInfo);
        Driver.StartBrowser(browserType);
    }

    public override void PostTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        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 values from the attributes are extracted via Reflection.

var executionBrowserAttribute = memberInfo.GetCustomAttribute<ExecutionBrowserAttribute>(true);

The current observer uses the singleton class Driver to access the Web Driver configurations. In the PreTestInit phase, it tells the driver to start a new browser instance of the specified browser type. In the PostTestCleanup it calls the same class to stop and dispose the browser instance.

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.

Throw a New Exception if There Is No Owner Attribute Set

The second observer class part of the observer design pattern is going to fail the current test if the Owner attribute is not set.

public class OwnerTestBehaviorObserver : BaseTestBehaviorObserver
{
    public OwnerTestBehaviorObserver(ITestExecutionSubject testExecutionSubject)
        : base(testExecutionSubject)
    {
    }

    public override void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
        this.ThrowExceptionIfOwnerAttributeNotSet(memberInfo);
    }

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

Again the information about the test’s attribute is retrieved via Reflection. The above concrete observer is overriding only the PreTestInit method. If the method detects in this phase  that there isn’t such attribute, a new exception is going to be thrown.

Extendable Test Execution in BaseTest via Observer Design Pattern

All of the previously mentioned logic should be combined together. The job is handled by the BaseTest class which is the parent class for all tests.

[TestClass]
public class BaseTest
{
    private readonly ITestExecutionSubject currentTestExecutionSubject;
    private TestContext testContextInstance;

    public BaseTest()
    {
        this.currentTestExecutionSubject = new MSTestExecutionSubject();
        this.InitializeTestExecutionBehaviorObservers(this.currentTestExecutionSubject);
        var memberInfo = MethodInfo.GetCurrentMethod();
        this.currentTestExecutionSubject.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.currentTestExecutionSubject.PreTestInit(this.TestContext, memberInfo);
        this.TestInit();
        this.currentTestExecutionSubject.PostTestInit(this.TestContext, memberInfo);
    }

    [TestCleanup]
    public void CoreTestCleanup()
    {
        var memberInfo = GetCurrentExecutionMethodInfo();
        this.currentTestExecutionSubject.PreTestCleanup(this.TestContext, memberInfo);
        this.TestCleanup();
        this.currentTestExecutionSubject.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(ITestExecutionSubject currentTestExecutionSubject)
    {
        new AssociatedBugTestBehaviorObserver(currentTestExecutionSubject);
        new BrowserLaunchTestBehaviorObserver(currentTestExecutionSubject);
        new OwnerTestBehaviorObserver(currentTestExecutionSubject);
    }
}

If the test classes need to add its own TestInit/TestCleanup logic, they need to override the TestInit/TestCleanup methods, the TestInitialize/TestCleanup attributes should not be used. In the base CoreTestInit method first are executed the PreTestInit methods of all observers with the help of the current subject class. After that is executed the TestInit method or its overridden version. Finally, all observers PostTestInit methods are executed. The same flow is valid for the cleanup methods. In the InitializeTestExecutionBehaviorObservers are created the instances of all desired observers through passing them the current subject as a parameter. After the base constructor is executed the TestContext property is populated from the MSTest execution engine. It is used to retrieve the currently executed test’s MemberInfo.

var memberInfo = this.GetType().GetMethod(this.TestContext.TestName);

If needed similar methods can be created for the class level initializations and cleanups.

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- Observer Design Pattern- 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)

Share

About the Author

Anton Angelov
CEO Automate The Planet
Bulgaria Bulgaria
Anton Angelov is an IT Consultant and Quality Assurance Architect at Innovative Lab. He is passionate about automation testing and designing test harness and tools, having the best industry development practices in mind. In addition, he is an active blogger and the founder of Automate The Planet. He strives to make the site one of the leading authorities in Automation Testing by presenting compelling articles, inspiring ardent discussions amongst the community. He is also one of the most-rated-answer authors of questions about Test Automation Frameworks (WebDriver) on Stack Overflow.

You may also be interested in...

Pro

Comments and Discussions

 
Questionimages missing Pin
BigTimber@home2-Jul-15 1:10
professionalBigTimber@home2-Jul-15 1:10 
AnswerRe: images missing Pin
Anton Angelov2-Jul-15 1:24
memberAnton Angelov2-Jul-15 1:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190306.1 | Last Updated 2 Jul 2015
Article Copyright 2015 by Anton Angelov
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid