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

通过事件进行高级观察者设计模式 - 设计模式自动化测试

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (10投票s)

2015年7月5日

Ms-PL

5分钟阅读

viewsIcon

14761

通过观察者设计模式在自动化测试中创建可扩展的测试执行。解释了 C# 中通过事件和委托的实现。

引言

在我的“自动化测试中的设计模式”系列文章中,我与您分享了如何将最有用的代码设计模式集成到自动化测试中的想法。在我上次的发表中,我向您解释了如何利用观察者设计模式的经典实现来创建可扩展的Web Driver测试执行引擎。在这里,我将向您展示如何使用 .NET 的事件和委托的强大功能来实现观察者设计模式的另一种更高级的实现。

UML 类图

参与者

参与此模式的类和对象是

  • ITestExecutionProvider – 对象使用此接口来注册为观察者,并从观察者中移除自身。
  • MSTestExecutionProvider – 具体提供者/主题始终实现 IProvider 接口。特定的提供者维护不同的通知方法,当状态更改时,这些方法用于更新所有已订阅的观察者。
  • TestExecutionEventsArgs – 用于将提供者状态传输到具体观察者的对象。
  • BaseTestBehaviorObserver – 所有潜在的观察者都需要继承基础观察者类。除了 SubscribeUnsubscribe 方法外,它还包含可以由具体观察者稍后覆盖的空方法。
  • OwnerTestBehaviorObserver – 具体观察者可以是实现 BaseObserver 类的任何类。每个观察者都与特定提供者注册,以便通过订阅提供者的事件来接收更新。
  • BaseTest – 框架中所有测试类的父类。使用 TestExecutionProvider 通过测试/类级别定义的属性和具体观察者来扩展其测试执行功能。

观察者设计模式 C# 代码

用例

示例代码的主要目标是为自动化工程师提供一种简单的方法,通过类/测试级别属性为当前测试执行添加额外的逻辑。例如,配置当前测试执行的浏览器或在未设置所有者属性时使测试失败。

将使用以下类结构

您可以在我之前的文章“观察者设计模式 - 设计模式自动化测试”中找到有关观察者设计模式经典实现的更多信息。 如果比较经典实现和基于事件的实现的 UML 图,您可能会注意到它们几乎相同。其中一个改变是缺少 ITestBehaviorObserver 接口。几乎所有其他更改都位于提供者类中,或者如之前所命名的主题类中。(主题和提供者这两个名称都是可能的)提供者的接口现在只包含具体观察者用于订阅各种更新点的事件。此外,提供者不包含任何 SubscribeUnsubscribe 方法。而是使用事件。

现在,此观察者设计模式实现中的提供者类的接口如下所示。

public interface IExecutionProvider
{
    event EventHandler<TestExecutionEventArgs> TestInstantiatedEvent;

    event EventHandler<TestExecutionEventArgs> PreTestInitEvent;

    event EventHandler<TestExecutionEventArgs> PostTestInitEvent;

    event EventHandler<TestExecutionEventArgs> PreTestCleanupEvent;

    event EventHandler<TestExecutionEventArgs> PostTestCleanupEvent;
}

具体提供者与之前开发的几乎相同,只有细微的更改。

public class MSTestExecutionProvider : IExecutionProvider
{
    public event EventHandler<TestExecutionEventArgs> TestInstantiatedEvent;

    public event EventHandler<TestExecutionEventArgs> PreTestInitEvent;

    public event EventHandler<TestExecutionEventArgs> PostTestInitEvent;

    public event EventHandler<TestExecutionEventArgs> PreTestCleanupEvent;

    public event EventHandler<TestExecutionEventArgs> PostTestCleanupEvent;

    public void PreTestInit(TestContext context, MemberInfo memberInfo)
    {
        this.RaiseTestEvent(this.PreTestInitEvent, context, memberInfo);
    }

    public void PostTestInit(TestContext context, MemberInfo memberInfo)
    {
        this.RaiseTestEvent(this.PostTestInitEvent, context, memberInfo);
    }

    public void PreTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        this.RaiseTestEvent(this.PreTestCleanupEvent, context, memberInfo);
    }

    public void PostTestCleanup(TestContext context, MemberInfo memberInfo)
    {
        this.RaiseTestEvent(this.PostTestCleanupEvent, context, memberInfo);
    }

    public void TestInstantiated(MemberInfo memberInfo)
    {
        this.RaiseTestEvent(this.TestInstantiatedEvent, null, memberInfo);
    }

    private void RaiseTestEvent(EventHandler<TestExecutionEventArgs> eventHandler, 
		TestContext testContext, MemberInfo memberInfo)
    {
        if (eventHandler != null)
        {
            eventHandler(this, new TestExecutionEventArgs(testContext, memberInfo));
        }
    }
}

在不同的测试执行点,使用 RaiseTestEvent 方法来通知该特定执行点所有已订阅的观察者。如果没有订阅者,则不触发事件。通过创建 TestExecutionEventArgs 类型的新对象来传递具体观察者所需的信息。

public class TestExecutionEventArgs : EventArgs
{
    private readonly TestContext testContext;
    private readonly MemberInfo memberInfo;

    public TestExecutionEventArgs(TestContext context, MemberInfo memberInfo)
    {
        this.testContext = context;
        this.memberInfo = memberInfo;
    }

    public MemberInfo MemberInfo
    {
        get
        {
            return this.memberInfo;
        }
    }

    public TestContext TestContext
    {
        get
        {
            return this.testContext;
        }
    }
}

它只包含两个属性。MSTest TestContextMemberInfo,即当前正在执行的测试方法的反射信息。

使用 .NET 事件和委托创建基础观察者

正如我已经在观察者设计模式的事件实现中所指出的,基础观察者类不需要实现任何接口。

public class BaseTestBehaviorObserver
{
    public void Subscribe(IExecutionProvider provider)
    {
        provider.TestInstantiatedEvent += this.TestInstantiated;
        provider.PreTestInitEvent += this.PreTestInit;
        provider.PostTestInitEvent += this.PostTestInit;
        provider.PreTestCleanupEvent += this.PreTestCleanup;
        provider.PostTestCleanupEvent += this.PostTestCleanup;
    }

    public void Unsubscribe(IExecutionProvider provider)
    {
        provider.TestInstantiatedEvent -= this.TestInstantiated;
        provider.PreTestInitEvent -= this.PreTestInit;
        provider.PostTestInitEvent -= this.PostTestInit;
        provider.PreTestCleanupEvent -= this.PreTestCleanup;
        provider.PostTestCleanupEvent -= this.PostTestCleanup;
    }

    protected virtual void TestInstantiated(object sender, TestExecutionEventArgs e)
    {
    }

    protected virtual void PreTestInit(object sender, TestExecutionEventArgs e)
    {
    }

    protected virtual void PostTestInit(object sender, TestExecutionEventArgs e)
    {
    }

    protected virtual void PreTestCleanup(object sender, TestExecutionEventArgs e)
    {
    }

    protected virtual void PostTestCleanup(object sender, TestExecutionEventArgs e)
    {
    }
}

Subscribe 方法中,具体观察者被订阅到所有可用的提供者事件。但是,连接的方法是空的。这使得特定的子观察者可以灵活地只覆盖所需的方法。这些父方法被标记为 protected,因此不能放入接口中。

顺便说一句,在我为“自动化测试中的设计模式”系列进行研究时,我总是先阅读几本书中介绍的模式。其中一本您可能想看看的是 Eric Freeman 的“Head First Design Patterns”。作者使用了一种独特的材料呈现方法,我以前从未见过。也许您中的大多数人会喜欢它。对于那些可能觉得这本书太简单的硬核粉丝,我推荐设计模式的圣经——《设计模式-可重用面向对象软件元素》。它将改变您对面向对象设计的看法。

创建由属性驱动的具体观察者

主要目标是创造一种方式,使用户能够通过属性来控制当前测试的执行浏览器类型。下面您可以看到 BaseTest 类和 ExecutionBrowser 属性的使用没有改变。

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

测试执行流程保持不变。具体观察者唯一的改变是覆盖的方法应该被标记为 protected 而不是 public

public class BrowserLaunchTestBehaviorObserver : BaseTestBehaviorObserver
{
    protected override void PreTestInit(object sender, TestExecutionEventArgs e)
    {
        var browserType = this.GetExecutionBrowser(e.MemberInfo);
        Driver.StartBrowser(browserType);
    }

    protected override void PostTestCleanup(object sender, TestExecutionEventArgs e)
    {
        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;
    }
}

控制浏览器类型的代码几乎与前面提到的区别相同。

将所有内容整合到 BaseTest 类中

通过使用单独的类来实现该模式,BaseTest 类几乎没有变化。只需替换具体提供者和观察者的实现。

public class BaseTest
{
    private readonly MSTestExecutionProvider currentTestExecutionProvider;
    private TestContext testContextInstance;

    public BaseTest()
    {
        this.currentTestExecutionProvider = new MSTestExecutionProvider();
        this.InitializeTestExecutionBehaviorObservers(this.currentTestExecutionProvider);
        var memberInfo = MethodInfo.GetCurrentMethod();
        this.currentTestExecutionProvider.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;
        }
    }

    [TestInitialize]
    public void CoreTestInit()
    {
        var memberInfo = GetCurrentExecutionMethodInfo();
        this.currentTestExecutionProvider.PreTestInit(this.TestContext, memberInfo);
        this.TestInit();
        this.currentTestExecutionProvider.PostTestInit(this.TestContext, memberInfo);
    }

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

到目前为止,“自动化测试中的设计模式”系列

  1. 页面对象模式
  2. 高级页面对象模式
  3. 门面设计模式
  4. Singleton 设计模式
  5. 流畅页面对象模式
  6. IoC 容器与页面对象
  7. 策略设计模式
  8. 高级策略设计模式
  9. 观察者设计模式
  10. 通过事件和委托实现观察者设计模式
  11. 通过 IObservable 和 IObserver 实现观察者设计模式
  12. 装饰器设计模式 - 混合策略
  13. 使代码更具可维护性的页面对象
  14. 改进的自动化测试外观设计模式 v.2.0
  15. 规则设计模式
  16. 规格设计模式
  17. 高级规格设计模式

 

如果你喜欢我的文章,请随意订阅
另外,请点击这些分享按钮。谢谢!

源代码

参考文献

这篇帖子-通过事件实现高级观察者设计模式 – 设计模式自动化测试 最初出现在 Automate The Planet

所有图片均从 DepositPhotos.com 购买,不能免费下载和使用。
许可协议

© . All rights reserved.