65.9K
CodeProject 正在变化。 阅读更多。
Home

自动化测试中的行为设计模式

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2016年5月8日

Ms-PL

5分钟阅读

viewsIcon

10480

了解如何使用行为设计模式来提高自动化测试 API 的灵活性。像搭乐高积木一样构建测试。此文章“自动化测试中的行为设计模式”首次发布于 Automate The Planet。

引言

我认为是时候停止通知您这是最受欢迎系列——自动化测试中的设计模式——的最新一期了。我所说的“行为设计模式”是一种您在所有官方设计模式列表中找不到的设计模式。但如您所知,设计模式是软件设计中常见问题的一种通用可重复解决方案。行为设计模式提高了自动化测试 API 的灵活性。您可以像搭建乐高积木一样构建测试。

定义


行为是小的可重用工作流。它们被组合在不同的测试中。通常,它们由一个特殊的引擎执行。

  • 灵活的自动化测试 API
  • 改进的测试可维护性
  • 提高了测试的可读性

UML 类图

Behaviours Design Pattern UML Class Diagram - click to enlarge image

参与者

参与行为设计模式的类和对象是

  • IBehaviour - 定义所有行为的接口。包含一个行为可以覆盖的所有方法。
  • Behaviour - 所有行为的基类,实现了 IBehaviour 接口,并提供了所有方法的空虚拟实现。
  • PurchaseTestContext - 静态类,包含单个购买相关测试的所有相关信息,如 URL、价格和折扣。
  • ItemPageNavigationBehaviour - ItemPage 类的具体行为。它包含导航逻辑。
  • ItemPage - 一个具体的页面对象,提供页面上可执行的不同服务操作。它在具体行为中使用。
  • BehaviourEngine - 执行行为工作流列表的类。

行为设计模式 C# 代码

行为设计模式是我们和我的队友在寻找更好的系统测试设计期间发现的一种模式。您在书中找不到它。有趣的是,当我的经理第一次提出这个想法时,他将该模式命名为乐高设计模式,因为您构建系统测试的方式与构建乐高积木的方式相同。

以前,我们有非常庞大的测试,其中包含大量的复杂工作流。然而,我们经常需要自定义它们。在过去的系统测试设计中,我们使用了外观设计模式。因此,对于每个自定义工作流,我们需要一个不同的外观方法。这导致文件庞大,测试可维护性差,灵活性不高。行为设计模式背后的主要思想是将庞大的工作流分解为多个有意义的小工作流,称为行为。例如,登录可以是一个单独的例程。

IBehaviour 接口

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

IBehaviour 接口定义了小的[_]工作流。您可以在 PostAct 阶段执行一个操作,然后等待某事发生。之后,您可以断言页面的状态。此外,您可以在初始操作之前验证状态。

基类行为

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

    public virtual void PerformPostAct()
    {
    }

    public virtual void PerformPostActAsserts()
    {
    }

    public virtual void PerformPreActAsserts()
    {
    }
}

Behaviour 实现 IBehaviour 接口。它只包含空虚拟方法。如果小[_]具体工作流缺少某些操作,它就不会覆盖它们。

具体行为

public class ItemPageBuyBehaviour : Behaviour
{
    private readonly ItemPage itemPage;

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

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

我正在自动化的测试用例再次是一个亚马逊在线购买。您可以在我关于空对象设计模式的文章中找到完整的测试用例描述。该行为包含对相关页面对象 ItemPage 的引用。一个初始化的页面实例被传递给构造函数。这是一个简单的[_]工作流,因为它只包含一个操作。然而,在某些情况下,您的行为中可能包含多个操作。例如,应用不同的折扣或断言多个税金。

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();
    }
}

这是一个更复杂的例子。主要操作是登录客户端,然后等待下一页加载。测试相关数据通过静态PurchaseTestContext 传递。

简单的行为引擎

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();
        }
    }
}

上面是 BehaviourEngine 的最简单实现。我们将当前测试用例的行为按顺序列表传递。然后通过 Activator 类创建行为。之后,它们的[_]操作按预期的工作流顺序执行。

测试中的简单行为引擎

[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));
}

首先,您需要初始化静态 PurchaseTestContext 类。之后,调用 SimpleBehaviourEngineExecute 方法。所需的测试[_]工作流被传递为行为类类型的有序列表。

通用的行为引擎

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));
        }
}

它基本上是同一个类加上额外的 20 个重载的通用方法。您可以使用这些通用方法,而不是通过 typeof 运算符传递所有类型。

测试中的通用行为引擎

[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>();
}

它需要更少的代码来配置测试[_]工作流。

覆盖小[_]工作流的一部分

有时,如果您可以只覆盖[_]工作流的一小部分,那会很方便。在某些情况下,您需要为一些不太常见的场景创建测试,所以您不想创建单独的可重用行为。

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();
        }
    }
}

上面是扩展行为引擎的代码,它可以包含被覆盖的部分。

测试中被覆盖操作的行为引擎

[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));
}

该测试与之前的测试几乎相同,除了 ConfigureCustomBehaviour 方法。在那里,您设置要覆盖的行为的类型。您不是替换整个[_]工作流,只是部分替换,所以您需要配置是哪一部分。最后,您传递一个匿名操作,该操作将代替默认操作执行。

使用 Unity IoC 容器改进行为引擎

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();
        }
    }
}

Unity IoC 容器代替 Activator 类来解析行为。

测试中的 Unity 行为引擎

[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));
}

测试中的用法与之前的完全相同,唯一的不同是您需要在容器中注册所有相关的类型。

自动化测试中的设计模式

此文章 自动化测试中的行为设计模式 首次发布于 Automate The Planet

自动化测试中的行为设计模式 - CodeProject - 代码之家
© . All rights reserved.