DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Composite Design Pattern in Java
  • Iterator Design Pattern In Java
  • Why We Still Struggle With Manual Test Execution in 2025
  • COM Design Pattern for Automation With Selenium and Cucumber

Trending

  • How to Create a Successful API Ecosystem
  • Apple and Anthropic Partner on AI-Powered Vibe-Coding Tool – Public Release TBD
  • Code Reviews: Building an AI-Powered GitHub Integration
  • Using Java Stream Gatherers To Improve Stateful Operations
  1. DZone
  2. Coding
  3. Languages
  4. Observer Design Patterns Automation Testing

Observer Design Patterns Automation Testing

In my articles from the series “Design Patterns in Automation Testing“, I show you how to integrate the most useful code design patterns in the automation testing.

By 
Anton Angelov user avatar
Anton Angelov
·
Jun. 29, 15 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
2.7K Views

Join the DZone community and get the full member experience.

Join For Free

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


So Far in the 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

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 loosley coupled designs between objects that interact.

·  Allows you to send data to many other objects in a very efficient manner.

·  No modification is need 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 theITestBehaviorObserver 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 enumBrowserTypes.

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

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 BaseTestclass 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 baseCoreTestInit 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 theInitializeTestExecutionBehaviorObservers are created the instances of all desired observers through passing them the current subject as a parameter. After the base constructor is executed theTestContext 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.

Source Code

You can download the full source code from my Github repository- https://github.com/angelovstanton/Projects/tree/master/PatternsInAutomation.Tests

If you like the article, please hit these share buttons. Thank you!

References:

·  SOLID Principles

·  Open Close Principle

·  MSDN The Open Closed Principle

·  Error! Hyperlink reference not valid.

·  MSDN Observer Design Pattern


·  Observer

Design Testing Attribute (computing) Object (computer science) Execution (computing) Interface (computing)

Published at DZone with permission of Anton Angelov, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Composite Design Pattern in Java
  • Iterator Design Pattern In Java
  • Why We Still Struggle With Manual Test Execution in 2025
  • COM Design Pattern for Automation With Selenium and Cucumber

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!