引言
在我之前“自动化测试中的设计模式”系列的文章中,我详细介绍了如何通过实现页面对象、外观模式和单例模式来改进自动化测试框架。在这里,我将通过将它们与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 时,我们想要检索 WikipediaMainPage。ContainerControlledLifetimeManager 参数用于指示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接口中,Navigate和Validate方法来自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 配置。
优点
+ 简化的类层次结构。
+ 可以更改页面对象的生命周期范围。
+ 更抽象的测试 - 可选地更改页面对象类。
到目前为止,“自动化测试中的设计模式”系列
- 页面对象模式
- 高级页面对象模式
- 门面设计模式
- Singleton 设计模式
- 流畅页面对象模式
- IoC 容器与页面对象
- 策略设计模式
- 高级策略设计模式
- 观察者设计模式
- 通过事件和委托实现观察者设计模式
- 通过 IObservable 和 IObserver 实现观察者设计模式
- 装饰器设计模式 - 混合策略
- 使代码更具可维护性的页面对象
- 改进的自动化测试外观设计模式 v.2.0
- 规则设计模式
- 规格设计模式
- 高级规格设计模式
如果你喜欢我的文章,请随意订阅
另外,请点击这些分享按钮。谢谢!
源代码
参考文献
CodeProject
所有图片均购自 DepositPhotos.com ,不可免费下载和使用。
许可协议
该帖子 使用 IoC 容器创建更强大的页面对象模式 原发布于 Automate The Planet。