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

失败测试分析——装饰器设计模式

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2016 年 9 月 19 日

Ms-PL

5分钟阅读

viewsIcon

7914

了解如何通过在测试失败时引入有意义的异常消息来改进自动化测试故障排除。该实用程序的第三个版本将利用装饰器设计模式。

在这里,我将向您介绍《自动化测试中的设计模式系列》的一部分,即《失败测试分析引擎》的第三个版本。我们将利用装饰器设计模式的强大功能来创建引擎的最改进版本。

 

定义

定义

装饰器设计模式动态地将附加职责附加到对象。装饰器为扩展功能提供了比子类化更灵活的替代方案。

UML 类图

参与者

参与此模式的类和对象是

  • 组件 – 定义可动态添加职责的对象的接口。
  • 装饰器 – 装饰器实现与它们将装饰的组件相同的接口(抽象类)。装饰器与它正在扩展的对象具有 HAS-A 关系,这意味着前者有一个实例变量,它持有对后者的引用。
  • 具体组件 – 将被动态增强的对象。它继承自组件。
  • 具体装饰器 – 装饰器可以增强组件的状态。它们可以添加新方法。新行为通常在组件中的现有方法之前或之后添加。

我们试图解决的问题是什么?

之前问题的两个解决方案都很好。但是,我认为它们在混合测试框架引擎中的集成存在问题。我不想修改现有的 ElementFinderService 并将其与异常分析引擎耦合。此外,在 Unity IoC 容器中注册命名实例并通过服务定位器解析它们似乎不是最佳可用解决方案。

用于失败测试分析的装饰器设计模式

IExceptionAnalysationHandler

public interface IExceptionAnalysationHandler
{
    bool IsApplicable(Exception ex = null, params object[] context);

    string DetailedIssueExplanation { get; }
}

IExceptionAnalysationHandler 是所有处理程序的主要接口。这是 Handler 基类之上的一个额外抽象。

IExceptionAnalyser

public interface IExceptionAnalyser
{
    void Analyse(Exception ex = null, params object[] context);

    void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>(
        IExceptionAnalysationHandler exceptionAnalysationHandler) 
        where TExceptionAnalysationHandler : IExceptionAnalysationHandler;

    void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>() 
        where TExceptionAnalysationHandler : IExceptionAnalysationHandler, new();

    void RemoveFirstExceptionAnalysationHandler();
}

在最初的两个解决方案中,ExceptionAnalyser 没有接口抽象。但是,我们需要它用于观察者版本。

ExceptionAnalyser

public class ExceptionAnalyser : IExceptionAnalyser
{
    private readonly List<IExceptionAnalysationHandler> exceptionAnalysationHandlers;

    public ExceptionAnalyser(IEnumerable<IExceptionAnalysationHandler> handlers)
    {
        this.exceptionAnalysationHandlers = new List<IExceptionAnalysationHandler>();
        this.exceptionAnalysationHandlers.AddRange(handlers);
    }

    public void RemoveFirstExceptionAnalysationHandler()
    {
        if (exceptionAnalysationHandlers.Count > 0)
        {
            exceptionAnalysationHandlers.RemoveAt(0);
        }
    }

    public void Analyse(Exception ex = null, params object[] context)
    {
        foreach (var exceptionHandler in exceptionAnalysationHandlers)
        {
            if (exceptionHandler.IsApplicable(ex, context))
            {
                throw new AnalyzedTestException(exceptionHandler.DetailedIssueExplanation, ex);
            }
        }
    }

    public void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>(
        IExceptionAnalysationHandler exceptionAnalysationHandler)
        where TExceptionAnalysationHandler : IExceptionAnalysationHandler
    {
        exceptionAnalysationHandlers.Insert(0, exceptionAnalysationHandler);
    }

    public void AddExceptionAnalysationHandler<TExceptionAnalysationHandler>()
        where TExceptionAnalysationHandler : IExceptionAnalysationHandler, new()
    {
        exceptionAnalysationHandlers.Insert(0, new TExceptionAnalysationHandler());
    }
}

如您所见,ExceptionAnalyser 的新改进版本不使用任何设计模式。当它开始分析异常时,它只是遍历所有处理程序的列表。此外,另一个重大变化是它不使用 Unity IoC 容器来创建处理程序。处理程序的主应用程序链通过构造函数作为集合传递。我稍后会向您展示如何通过 Unity 实现解析魔法。

IUiExceptionAnalyser

public interface IUiExceptionAnalyser : IExceptionAnalyser
{
    void AddExceptionAnalysationHandler(
        string textToSearchInSource, 
        string detailedIssueExplanation);
}

对于 UI 测试,我创建了新的 IUiExceptionAnalyser 接口,它定义了一个方法,用于注册新的自定义测试用例特定处理程序,以在 HTML 源代码中搜索特定文本。

UiExceptionAnalyser

public class UiExceptionAnalyser : ExceptionAnalyser, IUiExceptionAnalyser
{
    public UiExceptionAnalyser(IEnumerable<IExceptionAnalysationHandler> handlers) :
        base(handlers)
    {
    }

    public void AddExceptionAnalysationHandler(
        string textToSearchInSource, 
        string detailedIssueExplanation)
    {
        this.AddExceptionAnalysationHandler<CustomHtmlExceptionHandler>(
            new CustomHtmlExceptionHandler(textToSearchInSource, detailedIssueExplanation));
    }
}

UiExceptionAnalyser 的具体实现是 ExceptionAnalyser 的继承者,并且只添加了向链中添加新的 CustomHtmlExceptionHandlers 的方法。

HtmlSourceExceptionHandler

public abstract class HtmlSourceExceptionHandler : IExceptionAnalysationHandler
{
    public HtmlSourceExceptionHandler()
    {
    }

    public abstract string DetailedIssueExplanation { get; }

    public abstract string TextToSearchInSource { get; }

    public bool IsApplicable(Exception ex = null, params object[] context)
    {
        IBrowser browser = (IBrowser)context.FirstOrDefault();
        if (browser == null)
        {
            throw new ArgumentNullException("The browser cannot be null!");
        }
        bool result = browser.SourceString.Contains(this.TextToSearchInSource);
        return result;
    }
}

HtmlSourceExceptionHandler 的实现与之前提出的解决方案的实现相同,唯一的区别是它实现了 IExceptionAnalysationHandler。现在所有处理程序都实现了这个接口,因此主链可以通过 Unity 容器解析为 IEnumeeable 集合。

ExceptionAnalysedPage

public abstract class ExceptionAnalysedPage : BasePage
{
    public ExceptionAnalysedPage(
        IElementFinder elementFinder, 
        INavigationService navigationService)
    {
        var exceptionAnalyzedElementFinder =
            new ExceptionAnalyzedElementFinder(
                elementFinder as ExceptionAnalyzedElementFinder);
        var exceptionAnalyzedNavigationService =
            new ExceptionAnalyzedNavigationService(
                navigationService as ExceptionAnalyzedNavigationService,
                exceptionAnalyzedElementFinder.UiExceptionAnalyser);
        this.ExceptionAnalyser = exceptionAnalyzedElementFinder.UiExceptionAnalyser;
        this.ElementFinder = exceptionAnalyzedElementFinder;
        this.NavigationService = exceptionAnalyzedNavigationService;
    }

    public IUiExceptionAnalyser ExceptionAnalyser { get; private set; }
}

为了使页面能够使用异常分析引擎,它需要继承新的 ExceptionAnalysedPage 基类。它为所有页面添加了一个新属性 - UiExceptionAnalyser,以便它们可以在需要时添加自定义处理程序。此外,在这里创建了新的装饰器类,以便当找不到元素时调用异常分析引擎。导航引擎也是如此。如果等待特定 URL 失败,将调用引擎。最重要的是,页面和装饰器应该共享异常分析引擎的公共实例,因为我们将装饰器引擎的实例分配给基页的属性。否则,页面添加的处理程序将不会执行。

ExceptionAnalyzedElementFinder

下面您可以找到 ElementFinder 装饰器的代码。它包含原始 ElementFinder 的属性,并且或多或少地调用其方法。它实现了 IElementFinder 接口,这意味着它与原始 ElementFinder 类具有相同的方法。如果某些元素的定位失败,它会添加对 Analyse 方法的调用。

public class ExceptionAnalyzedElementFinder : IElementFinder
{
    public ExceptionAnalyzedElementFinder(
        IElementFinder elementFinder, 
        IUiExceptionAnalyser exceptionAnalyser)
    {
        this.ElementFinder = elementFinder;
        this.UiExceptionAnalyser = exceptionAnalyser;
    }

    public ExceptionAnalyzedElementFinder(
        ExceptionAnalyzedElementFinder elementFinderDecorator)
    {
        this.UiExceptionAnalyser = elementFinderDecorator.UiExceptionAnalyser;
        this.ElementFinder = elementFinderDecorator.ElementFinder;
    }

    public IUiExceptionAnalyser UiExceptionAnalyser { get; private set; }

    public IElementFinder ElementFinder { get; private set; }

    public TElement Find<TElement>(By by) where TElement : class,IElement
    {
        TElement result = default(TElement);
        try
        {
            result = this.ElementFinder.Find<TElement>(by);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.ElementFinder);
            throw;
        }

        return result;
    }

    public IEnumerable<TElement> FindAll<TElement>(By by) 
        where TElement : class, IElement
    {
        IEnumerable<TElement> result = default(IEnumerable<TElement>);
        try
        {
            result = this.ElementFinder.FindAll<TElement>(by);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.ElementFinder);
            throw;
        }

        return result;
    }

    public bool IsElementPresent(By by)
    {
        bool result = default(bool);
        try
        {
            result = this.ElementFinder.IsElementPresent(by);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.ElementFinder);
            throw;
        }

        return result;
    }
}

ExceptionAnalyzedNavigationService

NavigationService 装饰器的结构与 ElementFinderService 的结构相同。如果等待特定 URL 超时,我们将调用分析引擎。

public class ExceptionAnalyzedNavigationService : INavigationService
{
    public ExceptionAnalyzedNavigationService(
        INavigationService navigationService, 
        IUiExceptionAnalyser exceptionAnalyser)
    {
        this.NavigationService = navigationService;
        this.UiExceptionAnalyser = exceptionAnalyser;
    }

    public ExceptionAnalyzedNavigationService(
        ExceptionAnalyzedNavigationService exceptionAnalyzedNavigationService)
    {
        this.UiExceptionAnalyser = exceptionAnalyzedNavigationService.UiExceptionAnalyser;
        this.NavigationService = exceptionAnalyzedNavigationService.NavigationService;
    }

    public ExceptionAnalyzedNavigationService(
        ExceptionAnalyzedNavigationService exceptionAnalyzedNavigationService, 
        IUiExceptionAnalyser exceptionAnalyser)
    {
        this.UiExceptionAnalyser = exceptionAnalyser;
        this.NavigationService = exceptionAnalyzedNavigationService.NavigationService;
    }

    public IUiExceptionAnalyser UiExceptionAnalyser { get; private set; }

    public INavigationService NavigationService { get; private set; }

    public event EventHandler<PageEventArgs> Navigated;

    public string Url
    {
        get
        {
            return this.NavigationService.Url;
        }
    }

    public string Title
    {
        get
        {
            return this.NavigationService.Title;
        }
    }

    public void Navigate(string relativeUrl, string currentLocation, bool sslEnabled = false)
    {
        this.NavigationService.Navigate(relativeUrl, currentLocation, sslEnabled);
    }

    public void NavigateByAbsoluteUrl(string absoluteUrl, bool useDecodedUrl = true)
    {
        this.NavigationService.NavigateByAbsoluteUrl(absoluteUrl, useDecodedUrl);
    }

    public void Navigate(string currentLocation, bool sslEnabled = false)
    {
        this.NavigationService.Navigate(currentLocation, sslEnabled);
    }

    public void WaitForUrl(string url)
    {
        try
        {
            this.NavigationService.WaitForUrl(url);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.NavigationService);
            throw;
        }
    }

    public void WaitForPartialUrl(string url)
    {
        try
        {
            this.NavigationService.WaitForPartialUrl(url);
        }
        catch (Exception ex)
        {
            this.UiExceptionAnalyser.Analyse(ex, this.NavigationService);
            throw;
        }
    }
}

测试中的装饰器设计模式

ExecutionEngineBehaviorObserver

this.unityContainer.RegisterType<IEnumerable<IExceptionAnalysationHandler>, IExceptionAnalysationHandler[]>();
this.unityContainer.RegisterType<IUiExceptionAnalyser, UiExceptionAnalyser>();
this.unityContainer.RegisterType<IElementFinder, ExceptionAnalyzedElementFinder>(
    new InjectionFactory(x => new ExceptionAnalyzedElementFinder(this.driver, this.unityContainer.Resolve<IUiExceptionAnalyser>())));
this.unityContainer.RegisterType<INavigationService, ExceptionAnalyzedNavigationService>(
    new InjectionFactory(x => new ExceptionAnalyzedNavigationService(this.driver, this.unityContainer.Resolve<IUiExceptionAnalyser>())));

通过第一行代码,我们告诉 Unity,当需要 IExceptionAnalysationHandler 的 IEnumerable 集合时,从为此特定接口注册的所有类型创建它。由于装饰器没有空构造函数,我们需要指示 Unity 如何处理它们的初始化。为此,我们使用 InjectionFactory 对象。

LoginPage

public partial class LoginPage : ExceptionAnalysedPage
{
    public LoginPage(
        IElementFinder elementFinder,
        INavigationService navigationService) : base(elementFinder, navigationService)
    {
    }

    public void Navigate()
    {
        this.NavigationService.NavigateByAbsoluteUrl(@"https://www.telerik.com/login/");
    }

    public void Login()
    {
        this.LoginButton.Click();
    }
    
    public void Logout()
    {
        this.ExceptionAnalyser.
            AddExceptionAnalysationHandler<EmptyEmailValidationExceptionHandler>();
        this.LogoutButton.Click();
        this.ExceptionAnalyser.RemoveFirstExceptionAnalysationHandler();
    }
}

新页面属性的使用很简单。首先,页面需要继承自 ExceptionAnalysedPage 基类。然后,您首先添加一个自定义处理程序,并在不再需要时将其从链中删除。

LoginTelerikTests

[TestClass,
ExecutionEngineAttribute(ExecutionEngineType.TestStudio, Browsers.Firefox),
VideoRecordingAttribute(VideoRecordingMode.DoNotRecord)]
public class LoginTelerikTests : BaseTest
{
    public override void TestInit()
    {
        base.TestInit();
        UnityContainerFactory.GetContainer().
            RegisterType<IExceptionAnalysationHandler, ServiceUnavailableExceptionHandler>(
            Guid.NewGuid().ToString());
        UnityContainerFactory.GetContainer().
            RegisterType<IExceptionAnalysationHandler, FileNotFoundExceptionHandler>(
                Guid.NewGuid().ToString());  
    }

    [TestMethod]
    public void TryToLoginTelerik_Decorator()
    {
        var loginPage = this.Container.Resolve<LoginPage>();
        loginPage.Navigate();
        loginPage.Login();
        loginPage.Logout();
    }
}

除了 TestInit 方法,这里没有什么特别的。在那里,正如您所看到的,我们注册了两个全局处理程序,它们将为整个应用程序执行。因为我们都将它们注册为 IExceptionAnalysationHandler 接口,所以它们将作为 IEnumerable 集合传递给异常分析引擎。

自动化测试中的设计模式

参考

自动化测试中的装饰器设计模式

.NET 工厂装饰器

装饰器设计模式 – C#

文章失败测试分析 – 装饰器设计模式最初发表于Automate The Planet

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

© . All rights reserved.