Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Behaviours Design Pattern in Automated Testing

5.00/5 (5 votes)
8 May 2016Ms-PL5 min read 10.2K  
Learn how to use the Behaviours Design Pattern to increase the flexibility of your automated tests' API. Create tests the same way you build LEGO.The post Behaviours Design Pattern in Automated Testing appeared first on Automate The Planet.

Introduction

I think it is time to stop informing you that this is the newest edition to the most popular series- Design Patterns in Automated Testing. The so called by me Behaviours Design Pattern is a design pattern that you won't find in the list of all official design patterns. But as you know, the design pattern is a general repeatable solution to a commonly occurring problem in software design. Behaviours Design Pattern increases the flexibility of your automated tests' API. You can create tests in the same manner as if you build LEGO bricks.

Definition


The behaviours are small reusable workflows. They are combined in different tests. Usually, they are executed by a special engine.

  • Flexible automated tests' API
  • Improved tests' maintainability
  • Increased tests' readability

UML Class Diagram

Behaviours Design Pattern UML Class Diagram - click to enlarge image

Participants

The classes and objects participating in Behaviours Design Pattern are:

  • IBehaviour - Defines the interfaces for all behaviours. Contains all methods that one behaviour can override.
  • Behaviour - The base class for all behaviours implements IBehaviour interface and holds empty virtual implementations of all methods.
  • PurchaseTestContext - Static class that contains all relevant information for single purchase related test like URL, prices and discounts.
  • ItemPageNavigationBehaviour - A concrete behaviour for the ItemPage class. It holds a logic for navigation.
  • ItemPage - A concrete page object that provides different service operations that can be performed on the page. It is used in the concrete behaviours.
  • BehaviourEngine - It is the class that executes a list of behaviours' workflows.

Behaviours Design Pattern C# Code

Behaviours Design Pattern is a pattern that we discovered with my teammates during a period when we were searching for a better system tests' design. You won't find it in the books. A funny fact, when my manager first came up with this idea he named the pattern- Lego Design Pattern because you build the system tests in the same manner when you construct LEGO.

Previously, we had really huge tests that contain massive workflows. However, often we had to customise them. In the past system tests' design, we used the Facade Design Pattern. So for every custom workflow, we needed to have different facade method. This led to enormous files and less maintainable and not so flexible tests.The main idea behind the Behaviours Design Pattern is that the huge workflow is split in multiple meaningful smaller workflows called behaviours. For example, the login can be a separate routine.

IBehaviour Interface

C#
public interface IBehaviour
{
    void PerformAct();
    void PerformPostAct();
    void PerformPostActAsserts();
    void PerformPreActAsserts();
}

The IBehaviour interface defines the small workflow. You can perform an action than wait for something to happen in the PostAct phase. After that, you can assert the state of the page. Also, you can verify the state before the initial action.

Base Behaviour

C#
public class Behaviour : IBehaviour
{
    public virtual void PerformAct()
    {
    }

    public virtual void PerformPostAct()
    {
    }

    public virtual void PerformPostActAsserts()
    {
    }

    public virtual void PerformPreActAsserts()
    {
    }
}

Behaviour implements the IBehaviour interface. It only contains empty virtual methods. If the small concrete workflow doesn't have some of the operations, it doesn't override them.

Concrete Behaviours

C#
public class ItemPageBuyBehaviour : Behaviour
{
    private readonly ItemPage itemPage;

    public ItemPageBuyBehaviour(ItemPage itemPage)
    {
        this.itemPage = itemPage;
    }

    public override void PerformAct()
    {
        this.itemPage.ClickBuyNowButton();
    }
}

The test case that I am automating is once again an Amazon online purchase. You can find the full test case description in my article about the Null Object Design Pattern. The behaviour contains a reference to the related page object - ItemPage. An initialized instance of the page is passed to the constructor. This is a simple workflow because it holds only a single action. However, there are cases where you can have multiple actions inside your behaviours. For example, applying different discounts or asserting many taxes.

C#
public class SignInPageLoginBehaviour : Behaviour
{
    private readonly SignInPage signInPage;
    private readonly ShippingAddressPage shippingAddressPage;

    public SignInPageLoginBehaviour(
        SignInPage signInPage,
        ShippingAddressPage shippingAddressPage)
    {
        this.signInPage = signInPage;
        this.shippingAddressPage = shippingAddressPage;
    }

    public override void PerformAct()
    {
        this.signInPage.Login(
            PurchaseTestContext.ClientLoginInfo.Email,
            PurchaseTestContext.ClientLoginInfo.Password);
    }

    public override void PerformPostAct()
    {
        this.shippingAddressPage.WaitForPageToLoad();
    }
}

This is a more complicated example. The main action logs in the client then wait for the next page to load. The tests' related data is passed through the static class PurchaseTestContext.

Simple Behaviour Engine

C#
public static class SimpleBehaviourEngine
{
    public static void Execute(params Type[] pageBehaviours)
    {
        foreach (Type pageBehaviour in pageBehaviours)
        {
            var currentbehaviour = Activator.CreateInstance(pageBehaviour) as Behaviour;
            currentbehaviour.PerformPreActAsserts();
            currentbehaviour.PerformAct();
            currentbehaviour.PerformPostActAsserts();
            currentbehaviour.PerformPostAct();
        }
    }
}

Above, you can find the simplest implementation of BehaviourEngine. We pass the ordered list of behaviours for the current tests' test case. Then the behaviours are created via the Activator class. After that, their actions are performed in expected workflow's order.

Simple Behaviour Engine in Tests

C#
[TestMethod]
public void Purchase_SimpleBehaviourEngine()
{
    PurchaseTestContext.ItemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
    PurchaseTestContext.ItemPrice = "40.49";
    PurchaseTestContext.ClientPurchaseInfo = new ClientPurchaseInfo(
        new ClientAddressInfo()
        {
            FullName = "John Smith",
            Country = "United States",
            Address1 = "950 Avenue of the Americas",
            State = "New York",
            City = "New York City",
            Zip = "10001-2121",
            Phone = "00164644885569"
        });
    PurchaseTestContext.ClientPurchaseInfo.CouponCode = "99PERDIS";
    PurchaseTestContext.ClientLoginInfo = new ClientLoginInfo()
    {
        Email = "g3984159@trbvm.com",
        Password = "ASDFG_12345"
    };
    SimpleBehaviourEngine.Execute(
        typeof(ItemPageNavigationBehaviour),
        typeof(ItemPageBuyBehaviour),
        typeof(PreviewShoppingCartPageProceedBehaviour),
        typeof(SignInPageLoginBehaviour),
        typeof(ShippingAddressPageFillShippingBehaviour),
        typeof(ShippingAddressPageFillDifferentBillingBehaviour),
        typeof(ShippingAddressPageContinueBehaviour),
        typeof(ShippingPaymentPageContinueBehaviour),
        typeof(PlaceOrderPageAssertFinalAmountsBehaviour));
}

First, you need to initialize the static PurchaseTestContext class. After that, you call the Execute method of SimpleBehaviourEngine. The desired test's workflow is passed as an ordered list of behaviour classes' types.

Generic Behaviour Engine

C#
public static class GenericBehaviourEngine
{
    public static void Execute(params Type[] pageBehaviours)
    {
        foreach (Type pageBehaviour in pageBehaviours)
        {
            var currentbehaviour = Activator.CreateInstance(pageBehaviour) as Behaviour;
            currentbehaviour.PerformPreActAsserts();
            currentbehaviour.PerformAct();
            currentbehaviour.PerformPostActAsserts();
            currentbehaviour.PerformPostAct();
        }
    }

    public static void Execute<t1>()
        where T1 : Behaviour
    {
        Execute(typeof(T1));
    }

    public static void Execute<t1, t2="">()
        where T1 : Behaviour
        where T2 : Behaviour
    {
        Execute(typeof(T1), typeof(T2));
    }

    public static void Execute<t1, t2="" t3="">()
        where T1 : Behaviour
        where T2 : Behaviour
        where T3 : Behaviour
    {
        Execute(typeof(T1), typeof(T2), typeof(T3));
    }

    public static void Execute<t1, t2="">()
        where T1 : Behaviour
        where T2 : Behaviour
        where T3 : Behaviour
        where T4 : Behaviour
    {
        Execute(typeof(T1), typeof(T2), typeof(T3), typeof(T4));
    }

    // contains 15 more overloads...

      public static void Execute<t1, t2="">()
            where T1 : Behaviour
            where T2 : Behaviour
            where T3 : Behaviour
            where T4 : Behaviour
            where T5 : Behaviour
            where T6 : Behaviour
            where T7 : Behaviour
            where T8 : Behaviour
            where T9 : Behaviour
            where T10 : Behaviour
            where T11 : Behaviour
            where T12 : Behaviour
            where T13 : Behaviour
            where T14 : Behaviour
            where T15 : Behaviour
            where T16 : Behaviour
            where T17 : Behaviour
            where T18 : Behaviour
            where T19 : Behaviour
            where T20 : Behaviour
        {
            Execute(
                typeof(T1),
                typeof(T2),
                typeof(T3),
                typeof(T4),
                typeof(T5),
                typeof(T6),
                typeof(T7),
                typeof(T8),
                typeof(T9),
                typeof(T10),
                typeof(T12),
                typeof(T13),
                typeof(T14),
                typeof(T15),
                typeof(T16),
                typeof(T17),
                typeof(T18),
                typeof(T19),
                typeof(T20),
                typeof(T11));
        }
}

It is basically the same class plus additional 20 overloaded generic methods. Instead of passing all types using the typeof operator, you can use these generic methods.

Generic Behaviour Engine in Tests

C#
[TestMethod]
public void Purchase_GenericBehaviourEngine()
{
    PurchaseTestContext.ItemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
    PurchaseTestContext.ItemPrice = "40.49";
    PurchaseTestContext.ClientPurchaseInfo = new ClientPurchaseInfo(
        new ClientAddressInfo()
        {
            FullName = "John Smith",
            Country = "United States",
            Address1 = "950 Avenue of the Americas",
            State = "New York",
            City = "New York City",
            Zip = "10001-2121",
            Phone = "00164644885569"
        });
    PurchaseTestContext.ClientPurchaseInfo.CouponCode = "99PERDIS";
    PurchaseTestContext.ClientLoginInfo = new ClientLoginInfo()
    {
        Email = "g3984159@trbvm.com",
        Password = "ASDFG_12345"
    };
    GenericBehaviourEngine.Execute<
                                    ItemPageNavigationBehaviour,
                                    ItemPageBuyBehaviour,
                                    PreviewShoppingCartPageProceedBehaviour,
                                    SignInPageLoginBehaviour,
                                    ShippingAddressPageFillShippingBehaviour,
                                    ShippingAddressPageFillDifferentBillingBehaviour,
                                    ShippingAddressPageContinueBehaviour,
                                    ShippingPaymentPageContinueBehaviour,
                                    PlaceOrderPageAssertFinalAmountsBehaviour>();
}

It requires less code to configure the test's workflow.

Override Part of the Small Workflows

Sometimes, it comes in handy if you can override just a small part of the workflow. There are cases where you need to create a test for some less common scenario, so you don't want to create a separate reusable behaviour.

C#
public class OverridenActionsBehaviourEngine
{
    private readonly Dictionary<Type, Dictionary<BehaviourActions, Action>> 
    overridenBehavioursActions;

    public OverridenActionsBehaviourEngine()
    {
        this.overridenBehavioursActions = 
        new Dictionary<Type, Dictionary<BehaviourActions, Action>>();
    }

    public void Execute(params Type[] pageBehaviours)
    {
        foreach (Type pageBehaviour in pageBehaviours)
        {
            var currentbehaviour = Activator.CreateInstance(pageBehaviour) as Behaviour;
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.PreActAsserts,
                () => currentbehaviour.PerformPreActAsserts());
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.Act, 
                () => currentbehaviour.PerformAct());
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.PostActAsserts, 
                () => currentbehaviour.PerformPostActAsserts());
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.PostAct, 
                () => currentbehaviour.PerformPostAct());
        }
    }

    public void ConfugureCustomBehaviour<TBehavior>(
        BehaviourActions behaviourAction, 
        Action action)
        where TBehavior : IBehaviour
    {
        if (!this.overridenBehavioursActions.ContainsKey(typeof(TBehavior)))
        {
            this.overridenBehavioursActions.Add(
                typeof(TBehavior), 
                new Dictionary<BehaviourActions, Action>());
        }
        if (!this.overridenBehavioursActions[typeof(TBehavior)].ContainsKey(
            behaviourAction))
        {
            this.overridenBehavioursActions[typeof(TBehavior)].Add(behaviourAction, action);
        }
        else
        {
            this.overridenBehavioursActions[typeof(TBehavior)][behaviourAction] = action;
        }
    }

    private void ExecuteBehaviourOperation(
        Type pageBehaviour, 
        BehaviourActions behaviourAction, 
        Action defaultBehaviourOperation)
    {
        if (this.overridenBehavioursActions.ContainsKey(pageBehaviour.GetType()) &&
            this.overridenBehavioursActions[pageBehaviour.GetType()].ContainsKey(
                behaviourAction))
        {
            this.overridenBehavioursActions[pageBehaviour.GetType()][behaviourAction].Invoke();
        }
        else
        {
            defaultBehaviourOperation.Invoke();
        }
    }
}

Above is the code of the extended behaviour engine that can contain overridden parts.

Overridden Actions Behaviour Engine in Tests

C#
[TestMethod]
public void Purchase_OverridenActionsBehaviourEngine()
{
    PurchaseTestContext.ItemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
    PurchaseTestContext.ItemPrice = "40.49";
    PurchaseTestContext.ClientPurchaseInfo = new ClientPurchaseInfo(
        new ClientAddressInfo()
        {
            FullName = "John Smith",
            Country = "United States",
            Address1 = "950 Avenue of the Americas",
            State = "New York",
            City = "New York City",
            Zip = "10001-2121",
            Phone = "00164644885569"
        });
    PurchaseTestContext.ClientPurchaseInfo.CouponCode = "99PERDIS";
    PurchaseTestContext.ClientLoginInfo = new ClientLoginInfo()
    {
        Email = "g3984159@trbvm.com",
        Password = "ASDFG_12345"
    };
    var behaviourEngine = new OverriddenActionsBehaviourEngine();
    behaviourEngine.ConfugureCustomBehaviour<signinpageloginbehaviour>(
        BehaviourActions.PostAct,
        () => {
                // wait for different URL for this case.
        });
    behaviourEngine.Execute(
        typeof(ItemPageNavigationBehaviour),
        typeof(ItemPageBuyBehaviour),
        typeof(PreviewShoppingCartPageProceedBehaviour),
        typeof(SignInPageLoginBehaviour),
        typeof(ShippingAddressPageFillShippingBehaviour),
        typeof(ShippingAddressPageFillDifferentBillingBehaviour),
        typeof(ShippingAddressPageContinueBehaviour),
        typeof(ShippingPaymentPageContinueBehaviour),
        typeof(PlaceOrderPageAssertFinalAmountsBehaviour));
}

The test is almost identical to the previous ones except the ConfigureCustomBehaviour method. There, you set the type of the behaviour you want to override. You don't replace the whole workflow, only part of it, so you need to configure which is the part. Lastly, you pass an anonymous action that is going to be executed instead of the default action.

Improve Behaviour Engine with Unity IoC Container

C#
public class UnityBehaviourEngine
{
    private readonly IUnityContainer unityContainer;
    private readonly Dictionary<Type, Dictionary<BehaviourActions, Action>> 
    overridenBehavioursActions;

    public UnityBehaviourEngine(IUnityContainer unityContainer)
    {
        this.unityContainer = unityContainer;
        this.overridenBehavioursActions = 
        new Dictionary<Type, Dictionary<BehaviourActions, Action>>();
    }

    public void Execute(params Type[] pageBehaviours)
    {
        foreach (Type pageBehaviour in pageBehaviours)
        {
            var currentbehaviour = this.unityContainer.Resolve(pageBehaviour) as Behaviour;
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.PreActAsserts,
                () => currentbehaviour.PerformPreActAsserts());
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.Act, 
                () => currentbehaviour.PerformAct());
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.PostActAsserts,
                () => currentbehaviour.PerformPostActAsserts());
            this.ExecuteBehaviourOperation(
                pageBehaviour, 
                BehaviourActions.PostAct, 
                () => currentbehaviour.PerformPostAct());
        }
    }

    public void ConfugureCustomBehaviour<TBehavior>(
        BehaviourActions behaviourAction, 
        Action action)
        where TBehavior : IBehaviour
    {
        if (!this.overridenBehavioursActions.ContainsKey(typeof(TBehavior)))
        {
            this.overridenBehavioursActions.Add(typeof(TBehavior), 
            new Dictionary<BehaviourActions, Action>());
        }
        if (!this.overridenBehavioursActions[typeof(TBehavior)].ContainsKey(behaviourAction))
        {
            this.overridenBehavioursActions[typeof(TBehavior)].Add(behaviourAction, action);
        }
        else
        {
            this.overridenBehavioursActions[typeof(TBehavior)][behaviourAction] = action;
        }
    }

    private void ExecuteBehaviourOperation(
        Type pageBehaviour, 
        BehaviourActions behaviourAction, 
        Action defaultBehaviourOperation)
    {
        if (this.overridenBehavioursActions.ContainsKey(pageBehaviour.GetType()) &&
            this.overridenBehavioursActions[pageBehaviour.GetType()].ContainsKey(
                behaviourAction))
        {
            this.overridenBehavioursActions[pageBehaviour.GetType()][behaviourAction].Invoke();
        }
        else
        {
            defaultBehaviourOperation.Invoke();
        }
    }
}

Instead of the Activator class, the Unity IoC container resolves the behaviours.

Unity Behaviour Engine in Tests

C#
[TestMethod]
public void Purchase_UnityBehaviourEngine()
{
    PurchaseTestContext.ItemUrl = "/Selenium-Testing-Cookbook-Gundecha-Unmesh/dp/1849515743";
    PurchaseTestContext.ItemPrice = "40.49";
    PurchaseTestContext.ClientPurchaseInfo = new ClientPurchaseInfo(
        new ClientAddressInfo()
        {
            FullName = "John Smith",
            Country = "United States",
            Address1 = "950 Avenue of the Americas",
            State = "New York",
            City = "New York City",
            Zip = "10001-2121",
            Phone = "00164644885569"
        });
    PurchaseTestContext.ClientPurchaseInfo.CouponCode = "99PERDIS";
    PurchaseTestContext.ClientLoginInfo = new ClientLoginInfo()
    {
        Email = "g3984159@trbvm.com",
        Password = "ASDFG_12345"
    };
    var behaviourEngine = new UnityBehaviourEngine(container);
    behaviourEngine.ConfugureCustomBehaviour<signinpageloginbehaviour>(
        BehaviourActions.PostAct,
        () => {
                // wait for different URL for this case.
        });
    behaviourEngine.Execute(
        typeof(ItemPageNavigationBehaviour),
        typeof(ItemPageBuyBehaviour),
        typeof(PreviewShoppingCartPageProceedBehaviour),
        typeof(SignInPageLoginBehaviour),
        typeof(ShippingAddressPageFillShippingBehaviour),
        typeof(ShippingAddressPageFillDifferentBillingBehaviour),
        typeof(ShippingAddressPageContinueBehaviour),
        typeof(ShippingPaymentPageContinueBehaviour),
        typeof(PlaceOrderPageAssertFinalAmountsBehaviour));
}

The usage in tests is absolutely identical to the previous one with the only different that you need to register all related types in the container.

Design Patterns in Automated Testing

The post Behaviours Design Pattern in Automated Testing appeared first on Automate The Planet.

License

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