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

使用 IoC 容器创建更强大的页面对象模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (7投票s)

2015 年 9 月 24 日

Ms-PL

7分钟阅读

viewsIcon

14245

基于页面对象模式创建自动化框架。详细介绍如何通过 IoC 容器创建单例页面。

引言

在我之前“自动化测试中的设计模式”系列的文章中,我详细介绍了如何通过实现页面对象外观模式单例模式来改进自动化测试框架。在这里,我将通过将它们与Unity IoC 容器控制反转)结合起来,来改进这些模式。这会稍微改变类的层次结构,并移除一些基类和泛型参数。最终,代码的可读性会更好。

IoC 容器

维基百科的定义

引用

软件工程中,控制反转IoC)描述了一种设计,在这种设计中,自定义编写的计算机程序从通用的、可重用库接收控制流。具有这种设计的软件架构与传统的过程式编程相比,是反转了控制:在传统编程中,表达程序目的的自定义代码调用可重用库来处理通用任务,但在控制反转中,是可重用代码调用自定义的或任务特定的代码。

控制反转服务的以下设计目的

  • 将任务的执行与实现解耦。
  • 使模块专注于设计的任务。
  • 使模块摆脱对其他系统如何工作的假设,而是依赖于契约
  • 在替换模块时防止副作用

UML 类图

参与者

参与此模式的类和对象是

  • 页面对象(WikipediaMainPage)- 包含可以在页面上执行的操作,如搜索和导航。它通过Validate() 方法方便地访问页面验证器。页面对象模式的最佳实现隐藏了元素映射的使用,通过所有操作方法将其包装起来。
  • ICustomPage – 仅包含相应页面对象的方法的签名。
  • BasePage<S, M> – 提供对子页面元素映射类的访问,并定义标准的导航操作。
  • BasePage<M, V> – 通过Validate方法向子页面的验证器类添加一个实例。
  • BaseElementMap – 提供对当前浏览器的更便捷访问,以及在不同框架之间切换的功能。
  • BasePageValidator<M> – 为所有子验证器提供当前元素映射和页面对象本身的实例。

IoC 容器和页面对象 C# 代码

测试用例

示例的主要目标是在维基百科中搜索一个术语,并验证找到的文章页面的功能。

1. 导航到维基百科网站

2. 搜索术语

3. 验证“内容”隐藏切换

4. 点击“内容”隐藏链接

5. 验证“内容”列表不可见

如果我们不使用IoC 容器,我们的测试将如下代码所示。

[TestClass]
public class UnityWikipediaTests
{
    [TestInitialize]
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    [TestCleanup]
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    [TestMethod]
    public void TestWikiContentsToggle()
    {
        WikipediaMainPage wikiPage = new WikipediaMainPage();
        wikiPage.Navigate();
        wikiPage.Search("Quality assurance");
        wikiPage.Validate().ToogleLinkTextHide();
        wikiPage.Validate().ContentsListVisible();
        wikiPage.ToggleContents();
        wikiPage.Validate().ToogleLinkTextShow();
        wikiPage.Validate().ContentsListHidden();
    }
}

主要缺点是需要为每个测试创建 WikipediaMainPage。正如我在之前关于单例页面对象的文章中所讨论的,核心框架类可以修改为每个测试运行只生成一个页面实例。然而,帖子中探讨的方法通过多个新的泛型参数和类型,带来了类层次结构的巨大复杂性。请看这个简短的代码示例。

public abstract class BasePageSingletonDerived<S, M> : ThreadSafeNestedContructorsBaseSingleton<S>
    where M : BasePageElementMap, new()
    where S : BasePageSingletonDerived<S, M>
{
    protected M Map
    {
        get
        {
            return new M();
        }
    }

    public virtual void Navigate(string url = "")
    {
        Driver.Browser.Navigate().GoToUrl(string.Concat(url));
    }
}

public abstract class BasePageSingletonDerived<S, M, V> : BasePageSingletonDerived<S, M>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<M>, new()
    where S : BasePageSingletonDerived<S, M, V>
{
    public V Validate()
    {
        return new V();
    }
}

 通过使用Unity IoC 容器创建页面对象,可以简化框架的架构。为了实现这一点,需要对主页面对象类进行一些小的更改。在这个例子中,我将使用 BasePages 的简化版本。

public class BasePage<M>
    where M : BasePageElementMap, new()
{
    protected readonly string url;

    public BasePage(string url)
    {
        this.url = url;
    }

    public BasePage()
    {
        this.url = null;
    }

    protected M Map
    {
        get
        {
            return new M();
        }
    }

    public virtual void Navigate(string part = "")
    {
        Driver.Browser.Navigate().GoToUrl(string.Concat(url, part));
    }
}

public class BasePage<M, V> : BasePage<M>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<M>, new()
{
    public BasePage(string url) : base(url)
    {
    }

    public BasePage()
    {
    }

    public V Validate()
    {
        return new V();
    }
}

正如你所见,继承自单例类已经移除,一半的泛型参数也是如此。

为了能够在测试中使用IoC 容器WikipediaMainPage应该实现一个接口。

public class WikipediaMainPage : BasePage<WikipediaMainPageMap, WikipediaMainPageValidator>, IWikipediaMainPage
{
    public WikipediaMainPage()
        : base(@"https://en.wikipedia.org")
    {
    }

    public void Search(string textToType)
    {
        this.Map.SearchBox.Clear();
        this.Map.SearchBox.SendKeys(textToType);
        this.Map.SearchBox.Click();
    }

    public void ToggleContents()
    {
        this.Map.ContentsToggleLink.Click();
    }
}

与页面对象模式的典型实现相比,唯一的区别是IWikipediaMainPage接口。

public interface IWikipediaMainPage
{
    void Navigate(string part = "");

    WikipediaMainPageValidator Validate();

    void Search(string textToType);

    void ToggleContents();
}

它应该只包含页面对象的公共成员。我想强调的是,Map属性在这里不存在,因为它直接在测试中使用是一个坏习惯。

IoC 容器页面对象在测试中

在这个例子中,我使用的是 Unity IoC 容器的简化注册过程。在后续的示例中,我将展示如何通过 XML 配置来配置它,并将逻辑抽象到一个新的PageFactory类中。

[TestClass]
public class UnityWikipediaTests
{
    private static IUnityContainer pageFactory = new UnityContainer();

    [AssemblyInitialize()]
    public static void MyTestInitialize(TestContext testContext)
    {
        pageFactory.RegisterType<IWikipediaMainPage, WikipediaMainPage>(new ContainerControlledLifetimeManager());
    }

    [TestInitialize]
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    [TestCleanup]
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    [TestMethod]
    public void TestWikiContentsToggle_Unity()
    {
        var wikiPage = pageFactory.Resolve<IWikipediaMainPage>();
        wikiPage.Navigate();
        wikiPage.Search("Quality assurance");
        wikiPage.Validate().ToogleLinkTextHide();
        wikiPage.Validate().ContentsListVisible();
        wikiPage.ToggleContents();
        wikiPage.Validate().ToogleLinkTextShow();
        wikiPage.Validate().ContentsListHidden();
    }
}

AssemblyInitialize 标记表示该方法将只为当前程序集执行一次,这意味着注册过程将只执行一次。

通过以下一行

pageFactory.RegisterType<IWikipediaMainPage, WikipediaMainPage>(new ContainerControlledLifetimeManager());

我们告诉 unity,当我们解析接口 IWikipediaMainPage 时,我们想要检索 WikipediaMainPageContainerControlledLifetimeManager 参数用于指示IoC 容器将对象视为单例。这意味着如果我们第二次解析接口,将返回相同的实例。

通过 XML 配置 Unity IoC 容器

首先创建一个新的 XML 文件,命名为 unity.config 将其标记为始终复制到输出文件夹。

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>
  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <container>
      <register type="PatternsInAutomation.Tests.Advanced.Unity.WikipediaMainPage.IWikipediaMainPage, PatternsInAutomation.Tests"
                mapTo="PatternsInAutomation.Tests.Advanced.Unity.WikipediaMainPage.WikipediaMainPage, PatternsInAutomation.Tests">
        <lifetime type="singleton"/>
      </register>
    </container>
  </unity>
</configuration>

unity 部分下,粘贴 register 部分,其中你需要告诉 Unity 如何映射你的接口。格式如下:type=”FullNamespace.InterfaceName, AssemblyName” mapTo =”FullNamespace.ClassName, AssemblyName”。

<lifetime type="singleton"/>

上面的行告诉IoC 容器将指定的类创建为单例

为了让 Unity 容器能够从文件中检索所需的配置,你需要配置 IoC 容器使其能够使用它。一种方法是将代码添加到 AssemlyInitialize 方法中。

private static IUnityContainer pageFactory = new UnityContainer();

[AssemblyInitialize()]
public static void MyTestInitialize(TestContext testContext)
{
    var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = "unity.config" };
    Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
    var unitySection = (UnityConfigurationSection)configuration.GetSection("unity");
    pageFactory.LoadConfiguration(unitySection);
}

如果测试使用配置文件,则可以删除以下行。

pageFactory.RegisterType<IWikipediaMainPage, WikipediaMainPage>(new ContainerControlledLifetimeManager());

进一步改进 IoC 容器页面对象 - PageFactory

坦白说,当我第一次阅读直接使用 Unity 解析页面对象的代码时,我并不太喜欢。因此,我创建了一个类,其主要目标是隐藏 IoC 容器的工作及其配置。

public static class PageFactory
{
    private static IUnityContainer container; 

    static PageFactory()
    {
        var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = "unity.config" };
        Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
        var unitySection = (UnityConfigurationSection)configuration.GetSection("unity");
        container = new UnityContainer(); 
        container.LoadConfiguration(unitySection);
    }

    public static T Get<T>()
    {
        return container.Resolve<T>();
    }
}

而不是在 AssemblyInitialize 方法中配置 Unity,静态构造函数将只调用一次,从而加载所有必要的设置。

使用 IoC 容器 PageFactory

[TestClass]
public class UnityWikipediaTests
{
    [TestInitialize]
    public void SetupTest()
    {
        Driver.StartBrowser();
    }

    [TestCleanup]
    public void TeardownTest()
    {
        Driver.StopBrowser();
    }

    [TestMethod]
    public void TestWikiContentsToggle_Unity()
    {
        var wikiPage = PageFactory.Get<IWikipediaMainPage>();
        ////var wikiPage = pageFactory.Resolve<IWikipediaMainPage>();
        wikiPage.Navigate();
        wikiPage.Search("Quality assurance");
        wikiPage.Validate().ToogleLinkTextHide();
        wikiPage.Validate().ContentsListVisible();
        wikiPage.ToggleContents();
        wikiPage.Validate().ToogleLinkTextShow();
        wikiPage.Validate().ContentsListHidden();
    }
}

这样代码就更简洁易读了。

通过继承“改进”页面接口

在前面提到的IWikipediaMainPage接口中,NavigateValidate方法来自BasePage类。这意味着它们也应该存在于每个页面的接口中。就像有基类来保存这两个标准方法一样,也可以有一个基接口。

public interface IPage<M, V>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<M>, new()
{
    V Validate();

    void Navigate(string part = "");
}

IWikipediaMainPage接口的重构版本将如下所示。

public interface IWikipediaMainPage<M, V> : IPage<M, V>
    where M : BasePageElementMap, new()
    where V : BasePageValidator<M>, new() 
{
    void Search(string textToType);

    void ToggleContents();
}

我个人认为每次都添加这两个方法比让代码过于复杂要好。但是,你总有选择。

IoC 容器页面对象与单例页面对象相比的优缺点

缺点

- 依赖于 Unity IoC 容器。

- 较难使用。每次都需要解析接口来获取页面。

- 通过接口添加代码。

- 需要额外的代码或 XML 配置。

优点

+ 简化的类层次结构。

+ 可以更改页面对象的生命周期范围。

+ 更抽象的测试 - 可选地更改页面对象类。

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

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

 

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

源代码

参考文献

 

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

该帖子 使用 IoC 容器创建更强大的页面对象模式 原发布于 Automate The Planet

© . All rights reserved.