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

Failed Tests Аnalysis- Ambient Context Design Pattern

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
11 Sep 2016Ms-PL5 min read 7.6K   2  
Learn how to improve automated testing troubleshooting through the introduction of meaningful exceptions' messages on tests' failure. The second version of the utility will utilize the Ambient Design Pattern.

Introduction

Here, I will present to you the second version of the Failed Tests Analysis engine part of the Design Patterns in Au?tomated Testing Series. The first version of the engine utilized the Chain of Responsibility Design Pattern, however it has some drawbacks that I will mention in this publication. I decided that we need a more easy to use solution, so I developed the second version of the engine using the Ambient Context Design Pattern.

Definition

Provide a place to store scope or context related information or functionality that automatically follows the flow of execution between execution scopes or domains.

UML Class Diagram

Image 1

Participants

The classes and objects participating in the Ambient Design Pattern are:

  • TestExceptionsAnalyzerContext - The main class that does most of the work. It holds a static reference to the scope stack where the chain of handlers is kept. It contains a method for adding new handlers in front of the chain. It implements the IDisposable interface so that it can be used through using statements. For usability purposes, the classes are create as a generic type where you can specify the type of the handler that needs to be added. There are successors of this class where you can specify more than one generic type.
  • Handler - Defines an interface for handling requests. Contains the HandlerRequest method.
  • ConcreteHandler - Holds the actual logic for handling a request. It has access to its successor. If it cannot manage the request itself, it passes the execution to its successor.

What Are the Problems That We Try to Solve?

The previous solution described in the previous article Failed Tests ?nalysis - Chain of Responsibility Design Pattern was fairly good. However, if you need to specify multiple custom test-case-specific handlers, you need to call multiple methods in the right order. So I believed that the usability of the API could be improved. I thought that it might be a good idea to use the built-in goodies of the C# language such as the using statements. Through them, the readability of the code is slightly improved. The reader can find almost immediately the different handlers' scopes.

Ambient Context Design Pattern for Failed Tests Analysis

TestsExceptionsAnalyzerContext

C#
public class TestsExceptionsAnalyzerContext<THandler> : IDisposable
    where THandler : Handler, new()
{
    private static readonly Stack<Handler> scopeStack = new Stack<Handler>();

    public TestsExceptionsAnalyzerContext()
    {
        this.AddHandlerInfrontOfChain<THandler>();
    }

    public void Dispose()
    {
        this.MakeSuccessorMainHandler();
    }

    protected void AddHandlerInfrontOfChain<TNewHandler>()
        where TNewHandler : Handler, new()
    {
        var mainApplicationHandler = UnityContainerFactory.GetContainer().Resolve<Handler>(
            ExceptionAnalyzerConstants.MainApplicationHandlerName);
        var newHandler = UnityContainerFactory.GetContainer().Resolve<TNewHandler>();
        newHandler.SetSuccessor(mainApplicationHandler);
        UnityContainerFactory.GetContainer().RegisterInstance<Handler>(
            ExceptionAnalyzerConstants.MainApplicationHandlerName, newHandler);
        scopeStack.Push(newHandler);
    }

    private void MakeSuccessorMainHandler()
    {
        for (int i = 0; i < this.GetType().GetGenericArguments().Length; i++)
        {
            var handler = scopeStack.Pop();
            UnityContainerFactory.GetContainer().RegisterInstance<Handler>(
                ExceptionAnalyzerConstants.MainApplicationHandlerName, handler.Successor);
            handler.ClearSuccessor();
        }
    }
}

The TestsExceptionsAnalyzerContext is the class where all of the magic is happening. As previously mentioned, here you can find a static stack variable that holds the chain of handlers. When you create the class with new generic type, it will add a new instance of it in front of the chain because the stack is a LIFO (last-in first-out) collection. We use Unity IoC container to get the primary handler of the application though an instance name - "MAIN_APP_HANDLER". When a new handler is moved on top of the stack, it is registered as the new main handler. Once the Dispose method of the IDisposable interface is called, the new main app handler is the successor of the current primary handler. This way, once you leave the using statement's scope, the created custom handler is no more valid.

ExceptionAnalyzerConstants

C#
public class ExceptionAnalyzerConstants
{
    public const string MainApplicationHandlerName = @"MAIN_APP_HANDLER";
}

Through this class, we can reuse the name of the primary handler registered in Unity.

TestsExceptionsAnalyzerContext<THandler1, THandler2>

C#
public class TestsExceptionsAnalyzerContext<THandler1, THandler2> : 
    TestsExceptionsAnalyzerContext<THandler1>
    where THandler1 : Handler, new()
    where THandler2 : Handler, new()
{
    public TestsExceptionsAnalyzerContext()
    {
        this.AddHandlerInfrontOfChain<THandler2>();
    }
}

As I mentioned earlier, there are successors of the TestsExceptionsAnalyzerContext base class. This particular class adds two handlers to the static stack. The order depends on the order of the specified generic types.

TestsExceptionsAnalyzerContext<THandler1, THandler2, THandler3>

C#
public class TestsExceptionsAnalyzerContext<THandler1, THandler2, THandler3> : 
    TestsExceptionsAnalyzerContext<THandler1, THandler2>
    where THandler1 : Handler, new()
    where THandler2 : Handler, new()
    where THandler3 : Handler, new()
{
    public TestsExceptionsAnalyzerContext()
    {
        this.AddHandlerInfrontOfChain<THandler3>();
    }
}

You can add even three handlers using a single TestsExceptionsAnalyzerContext.

ExceptionAnalyzer

C#
public static class ExceptionAnalyzer
{
    public static void AnalyzeFor<TExceptionHander>(Action action)
        where TExceptionHander : Handler, new()
    {
        using (UnityContainerFactory.GetContainer().
            Resolve<TestsExceptionsAnalyzerContext<TExceptionHander>>())
        {
            action();
        }
    }

    public static void AnalyzeFor<TExceptionHander1, TExceptionHander2>(Action action)
        where TExceptionHander1 : Handler, new()
        where TExceptionHander2 : Handler, new()
    {
        using (UnityContainerFactory.GetContainer().
            Resolve<TestsExceptionsAnalyzerContext<TExceptionHander1, TExceptionHander2>>())
        {
            action();
        }
    }

    public static void AnalyzeFor<TExceptionHander1, TExceptionHander2, TExceptionHander3>(Action action)
        where TExceptionHander1 : Handler, new()
        where TExceptionHander2 : Handler, new()
        where TExceptionHander3 : Handler, new()
    {
        using (UnityContainerFactory.GetContainer().
            Resolve<TestsExceptionsAnalyzerContext
                   <TExceptionHander1, TExceptionHander2, TExceptionHander3>>())
        {
            action();
        }
    }
}

If you are not a fan of the curly brackets and using statements, this class is specially designed for you. This is a helper wrapper around the TestsExceptionsAnalyzerContext where you can specify a handler and a custom action. The specified handler will be valid only for the code of the anonymous action. I prefer the pure using statements approach.

Ambient Context Design Pattern in Tests

ExceptionAnalizedElementFinderService

C#
public class ExceptionAnalizedElementFinderService
{
    private readonly IUnityContainer container;
    private readonly IExceptionAnalyzer excepionAnalyzer;

    public ExceptionAnalizedElementFinderService(
        IUnityContainer container, 
        IExceptionAnalyzer excepionAnalyzer)
    {
        this.container = container;
        this.excepionAnalyzer = excepionAnalyzer;
    }

    public TElement Find<TElement>(IDriver driver, Find findContext, Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        TElement result = default(TElement);
        try
        {
            string testingFrameworkExpression = by.ToTestingFrameworkExpression();
            this.WaitForExists(driver, testingFrameworkExpression);
            var element = findContext.ByExpression(by.ToTestingFrameworkExpression());
            result = this.ResolveElement<TElement>(driver, element);
        }
        catch (Exception ex)
        {
            # region 10. Failed Tests ?nalysis- Ambient Context Design Pattern
            var mainApplicationHandler = UnityContainerFactory.GetContainer().Resolve<Handler>(
                ExceptionAnalyzerConstants.MainApplicationHandlerName);
            mainApplicationHandler.HandleRequest(ex, driver);
            #endregion
            throw;
        }

        return result;
    }

    public IEnumerable<TElement> FindAll<TElement>(IDriver driver, Find findContext, Core.By by)
        where TElement : class, Core.Controls.IElement
    {
        List<TElement> resolvedElements = new List<TElement>();
        try
        {
            string testingFrameworkExpression = by.ToTestingFrameworkExpression();
            this.WaitForExists(driver, testingFrameworkExpression);
            var elements = findContext.AllByExpression(testingFrameworkExpression);

            foreach (var currentElement in elements)
            {
                TElement result = this.ResolveElement<TElement>(driver, currentElement);
                resolvedElements.Add(result);
            }
        }
        catch (Exception ex)
        {
            # region 10. Failed Tests ?nalysis- Ambient Context Design Pattern
            var mainApplicationHandler = UnityContainerFactory.GetContainer().Resolve<Handler>(
                ExceptionAnalyzerConstants.MainApplicationHandlerName);
            mainApplicationHandler.HandleRequest(ex, driver);
            #endregion
            throw;
        }

        return resolvedElements;
    }

    public bool IsElementPresent(Find findContext, Core.By by)
    {
        try
        {
            string controlFindExpression = by.ToTestingFrameworkExpression();
            Manager.Current.ActiveBrowser.RefreshDomTree();
            HtmlFindExpression hfe = new HtmlFindExpression(controlFindExpression);
            Manager.Current.ActiveBrowser.WaitForElement(hfe, 5000, false);
        }
        catch (TimeoutException)
        {
            return false;
        }

        return true;
    }

    private void WaitForExists(IDriver driver, string findExpression)
    {
        try
        {
            driver.WaitUntilReady();
            HtmlFindExpression hfe = new HtmlFindExpression(findExpression);
            Manager.Current.ActiveBrowser.WaitForElement(hfe, 5000, false);
        }
        catch (Exception)
        {
            this.ThrowTimeoutExceptionIfElementIsNull(driver, findExpression);
        }
    }

    private TElement ResolveElement<TElement>(
        IDriver driver,
        ArtOfTest.WebAii.ObjectModel.Element element)
        where TElement : class, Core.Controls.IElement
    {
        TElement result = this.container.Resolve<TElement>(
            new ResolverOverride[]
            {
                new ParameterOverride("driver", driver),
                new ParameterOverride("element", element),
                new ParameterOverride("container", this.container)
            });
        return result;
    }

    private void ThrowTimeoutExceptionIfElementIsNull(IDriver driver, params string[] customExpression)
    {
        StackTrace stackTrace = new StackTrace();
        StackFrame[] stackFrames = stackTrace.GetFrames();
        StackFrame callingFrame = stackFrames[3];
        MethodBase method = callingFrame.GetMethod();
        string currentUrl = driver.Url;
        throw new ElementTimeoutException(
            string.Format(
                "TIMED OUT- for element with Find Expression:\n {0}\n 
                      Element Name: {1}.{2}\n URL: {3}\nElement Timeout: {4}",
                string.Join(",", customExpression.Select(p => p.ToString()).ToArray()),
                method.ReflectedType.FullName, method.Name, currentUrl, 
                      Manager.Current.Settings.ElementWaitTimeout));
    }
}

The only difference compared to the previous solution here is how we get the main app handler. Instead of passing it as a parameter, this time, we resolve it through the named instance.

ExecutionEngineBehaviorObserver

C#
UnityContainerFactory.GetContainer().RegisterType<FileNotFoundExceptionHandler>(
    ExceptionAnalyzerConstants.MainApplicationHandlerName);
var mainHandler = UnityContainerFactory.GetContainer().Resolve<FileNotFoundExceptionHandler>();
UnityContainerFactory.GetContainer().RegisterInstance<Handler>(
    ExceptionAnalyzerConstants.MainApplicationHandlerName, 
    mainHandler, 
    new HierarchicalLifetimeManager());

The above code should be added to the ExecutionEngineBehaviourObser so that the primary chain of handlers can be initialized correctly. The handlers that are specified here won't be modified by the ambient context.

LoginTelerikTests

C#
[TestClass,
ExecutionEngineAttribute(ExecutionEngineType.TestStudio, Browsers.Firefox),
VideoRecordingAttribute(VideoRecordingMode.DoNotRecord)]
public class LoginTelerikTests : BaseTest
{
    [TestMethod]
    public void TryToLoginTelerik_AmbientContext()
    {
        this.Driver.NavigateByAbsoluteUrl("https://www.telerik.com/login/");
        using (new TestsExceptionsAnalyzerContext<EmptyEmailValidationExceptionHandler>())
        {
            var loginButton = this.Driver.FindByIdEndingWith<IButton>("LoginButton");
            loginButton.Click();
            var logoutButton = this.Driver.FindByIdEndingWith<IButton>("LogoutButton");
            logoutButton.Click();
        }
    }

    [TestMethod]
    public void TryToLoginTelerik_AmbientContextWrapper()
    {
        this.Driver.NavigateByAbsoluteUrl("https://www.telerik.com/login/");
        ExceptionAnalyzer.AnalyzeFor<EmptyEmailValidationExceptionHandler>(() =>
        {
            var loginButton = this.Driver.FindByIdEndingWith<IButton>("LoginButton");
            loginButton.Click();
            var logoutButton = this.Driver.FindByIdEndingWith<IButton>("LogoutButton");
            logoutButton.Click();
        });
    }
}

The first test method uses using statements to add a new custom handler. The second one utilizes the helper wrapper.

Design Patterns in Automated Testing

References

The post Failed Tests ?nalysis- Ambient Context Design Pattern 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 --