云层针对这篇文章加了一点自己的看法和调整!
云层:Selenium在某些角度已经是一个淘汰的技术了,但是并不妨碍大家重新看这篇文章来整下UI自动化框架实践体系。
本文作者: Thuc Nguyen, Truong Pham 翻译:陈晓鹏
https://www.logigear.com/blog/test-automation/building-a-selenium-framework-from-a-to-z/
引言
为什么是Selenium
Web开发和测试的需求是巨大的。截至2018年1月,全球互联网网站超过13亿,互联网用户超过38亿。因此,工具市场的竞争比以往任何时候都更加激烈。商业工具供应商为了在测试工具这块蛋糕上分得一杯羹,正在激烈地相互践踏。但到目前为止,在受欢迎程度方面,还没有工具能超越Selenium。
Selenium最大的优点是它是开源的。换句话说,它是完全免费下载和使用的。Selenium提供了一个名为WebDriver的API,它使测试人员能够用多种编程语言编写测试,包括Java、c#、Python等。
除了web浏览器应用,你还可以通过Appium实现Android和iOS等移动设备的自动化。所有这些能力触手可及,我们可能会觉得战无不胜。测试自动化现在就没有问题了吗?不幸的是,自动化从来就没有那么容易。
许多测试团队每天都在为测试的可维护性和可伸缩性而挣扎。通常,在经过第一个初始阶段之后,测试团队会后悔他们没有从一开始就花足够的时间和精力来学习如何构建一个好的框架。而如何构建一个好的自动化测试框架,正是本篇文章的意义所在。
01
选择一种编程语言
如果你会编程……
你选择的编程语言对你的框架设计和生产力有巨大的影响。因此,您应该问的第一个问题是:我想用什么编程语言编写测试?
Selenium社区中最流行的语言是Java、Python和JavaScript。要决定应该选择哪种编程语言,请考虑以下因素:
- 被测系统web应用程序使用了什么编程语言?
- 你的公司有可以重用的内部框架吗?
- 谁将使用您的框架来编写测试?
根据我们的经验,如果您从头开始一个新项目,Java是最安全的选择,因为Java由于跨平台工作而被社区广泛采用。此外,如果遇到问题,您可以很容易地找到代码示例或故障排除技巧。Java也是每个新版本Selenium的首要优先级。
如果你不擅长写代码……
好消息是:您还可以使用著名的行为驱动开发(BDD)方法编写Selenium测试。但这需要一些额外的设置。
简而言之,BDD通过将测试流组织成Given、When和Then (GWT)语句,帮助提高测试的可读性。因此,不仅具有编程技能的测试自动化工程师,而且领域专家和业务测试人员都可以理解测试,并对测试创建、测试结果调试和测试维护的过程做出有意义的贡献。
下图显示了一个用BDD编写的测试示例。
如果你选择BDD,你可以利用一些工具:
- Cucumber(支持大多数主要语言)
- SpecFlow (主要针对c#)
在我们看来,BDD适合于小型或短期项目。如果您必须使用GWT语法编写一打“And/And/And…”语句,那么就很难扩展,可能一个更成熟的方法是关键字驱动测试方法(KDT)。
云层:BDD基本在国内很难有使用价值,让产品来写BDD是几乎不太现实的,尽量用Java因为很少后台用Python写的,虽然Python开始写起来很快,除非你就不准备做大量UI自动化。
02
选择一个单元测试框架
现在我们已经选择了最合适的编程语言,我们需要选择一个单元测试框架来构建我们的框架。如果我们已经选择了Java语言来编写测试,我推荐TestNG,因为它提供了几个重要的好处,例如:
- TestNG类似于JUnit,但它比JUnit强大得多——特别是在测试集成类方面。而且TestNG继承了JUnit提供的所有好处。
- TestNG消除了旧框架的大多数限制,使您能够编写更灵活、更强大的测试。一些突出的特性是:简单的注释、分组、排序和参数化。
下面的代码片段显示了两个TestNG测试的示例。由于@BeforeClass和@AfterClass注释,这两个测试共享相同的setUp()和teardown()方法。
您可以将测试类看作是一些自动化测试用例的逻辑分组,它们具有相同的目标,或者至少具有相同的关注领域。
例如,您可以将关注于验证应用程序是否正确计算购物车总价的自动化测试用例分组到一个名为TotalPriceCalculation的测试类中。这些测试可能共享导航到被测电子商务网站的初始设置setup(),以及清除购物车中的商品的步骤teardown()。
使用TestNG,您还可以使用代码片段中演示的@Test注释将一个测试类中的测试分组到子组中。
云层:随着Junit5成熟,TestNG已经没有什么优势了!
03
设计框架架构
现在,是时候看看我们的框架架构了。我们已经提出了一个可持续的、可维护的、可伸缩的架构,如下图所示。我们强烈建议您遵循此体系结构,或者至少遵循其背后的核心原则。
这个架构的美妙之处在于它有两个独立的组件,分别叫做SeleniumCore和SeleniumTest。我们将在下面的部分中详细解释这些组件。简而言之,拥有两个分离的组件从长远来看可以简化测试维护。
例如,如果您想检查一个<input>标签在点击它之前是否在屏幕上是可见的,你可以简单地修改“input”元素包装,这个改变会被广播到所有的和此标签交互的测试用例或page object。
没有将测试和元素包装分离意味着在您想要引入新的业务逻辑时你必须更新每个与之交互的测试用例或page object。
现在我们已经对框架有了一个概述,我们将在这篇文章接下来的部分中研究如何构建每个组件。
04
构建SeleniumCore组件
SeleniumCore被设计用来管理浏览器实例以及元素交互。这个组件帮助您创建和销毁WebDriver对象。
一个WebDriver对象,顾名思义,“驱动”一个浏览器实例,比如从一个web页面移动到另一个web页面。理想情况下,测试编写者不应该关心如何创建或销毁浏览器实例。他们只需要一个WebDriver对象来执行测试流中给定的测试步骤。
为了实现这种抽象,我们通常遵循一种称为工厂设计模式的最佳实践。下面是一个类图,解释了我们如何在框架中使用工厂设计模式。
在上面的图中,LoginTest、LogoutTest和OrderTest是使用DriverManagerFactory来制造DriverManager对象的测试类。
在下面的代码片段中,您将看到DriverManager是一个抽象类,它的实现比如ChromeDriverManager, FirefoxDriverManager和EdgeDriverManager必须公开一组
强制方法如createWebDriver()、getWebDriver()和quitWebDriver()。
下面的ChromeDriverManager实现了上面代码片段中定义的DriverManager抽象类。具体来说,在createWebDriver()方法中,我们用一组预定义选项实例化一个新的ChromeDriver。同样,我们将对FirefoxDriverManager、EdgeDriverManager或任何其他您感兴趣的浏览器进行相同的操作。
为了方便地管理我们项目所关注的浏览器,我们定义了一个名为DriverType的枚举,它包含我们想要测试的所有浏览器。
如前所述,DriverManagerFactory是一个“制造”DriverManager对象的工厂。你用你的驱动类型(上面描述的)调用这个类的getDriverManager()方法来接收一个驱动管理器类型的对象。
由于DriverManager是一个抽象类,你不会收到一个实际的DriverManager,只是它的一个实现,如ChromeDriverManager, FireFoxDriverManager等。下面的代码片段演示了如何实现DriverManagerFactory类。
在理解了如何创建浏览器实例之后,我们现在将使用上面的DriverManager对象之一创建一个测试。正如你所看到的,脚本开发者并不关心Chrome的WebDriver是否叫ChromeDriver。当他们需要一个CHROME浏览器实例时,他们只需要指定简单的CHROME字符串(驱动类型枚举中的一个值)。
在下面的测试中,我们导航到www.google.com并验证站点的标题为“谷歌”。这不是一个测试,但它演示了如何应用前面提到的DriverManagerFactory。
通过使用这种工厂设计模式,如果在新的浏览器(例如Safari)上运行测试有新的需求,这应该不是什么大问题。我们只需要创建一个SafariDriverManger,它扩展了DriverManager,就像我们之前看到的ChromeDriverManager一样。在创建它时,测试编写人员可以使用驱动类型enum的新SAFARI值简单地创建一个SafariDriverManager。
类似地,当我们需要对移动本地应用程序或移动浏览器上的web应用程序运行测试时,很容易与Appium集成。我们可以实现一个新类,即iOSDriverManager。
05
构建SeleniumTest组件
与作为框架基础的SeleniumCore组件不同,SeleniumTest组件包含了所有使用SeleniumCore提供的类的测试用例。如前所述,我们在这里应用的设计模式称为PageObject模式(POM)。
PAGEOBJECT模式
页面对象模型(POM)已经成为测试自动化框架中实际使用的模式,因为它减少了代码的重复,从而降低了测试维护成本。
应用POM意味着我们将把UI元素组织到页面中。页面还可以包含在页面上执行的“操作”或业务流。例如,如果您的web应用程序包含多个页面,称为登录页面、主页、注册页面等,我们将为它们创建相应的pageobject,如LoginPage、HomePage、RegisterPage等。
由于POM的存在,如果任何页面的UI发生了变化,我们只需要更新有问题的PageObject一次,而不用费力地重构所有与该页面交互的测试。
下面的图片展示了我们通常如何构造PageObjects、它们的元素定位器以及动作方法。注意,尽管RegisterPage和LoginPage都有userNameTextBox和passwordTextBox,但这些web元素是完全不同的。注册页面上的userNameTextBox和passwordTextBox用于注册新帐户,而登录页面上的同一组控件允许用户登录到他们的帐户。
一个简单的页面对象
让我们放大到一个特定的页面对象。在下面的例子中,我们看到LoginPage包含了一些重要的信息:
- 接收WebDriver对象并将其内部WebDriver对象设置为该对象的构造函数。
- 帮助WebDriver对象找到你想与之交互的web元素的元素定位器。例如userNameTextBox
- 在登录页面上执行的方法,如setUserName()、setPassword()、clickLogin()和最重要的Login()方法,后者结合了上述三种方法。
云层:PO的主要理念,用类管理页面,用属性管理页面元素,用方法封装页面逻辑
如何使用PAGEOBJECT
要在测试中与登录页面交互,只需创建一个新的LoginPage对象并调用其操作方法。因为我们已经从测试编写器中抽象出了web元素定义(定位器),所以它们不需要知道如何找到元素,例如userNameTextBox。他们只是调用login()方法并传递一组用户名和密码。
如果web元素定义碰巧发生更改,我们不需要更新与此登录页面交互的所有测试。
正如您可能已经注意到的,测试的目标是验证当用户尝试使用不正确的凭据登录web应用程序时显示正确的错误消息(“无效的用户名或密码”)
注意,我们在之前的代码中并没有包括getLoginErrorMessage()操作方法,因为这种方法的实现可能会很复杂,这取决于我们怎样设计web应用程序。通常情况下,会出现一条错误消息作为一个简单的登录按钮旁边红色字符串。
在这种情况下,检索错误消息会更直接。我们只需要定义一个元素定位器,例如errorMessageLabel = By.id(" errorMessage ")),然后使用该定位器创建getLoginErrorMessage()方法。
至此,我们的测试自动化框架终于有了一个具体的基础。我们现在可以将其发布给团队,这样每个人都将为测试开发和测试执行工作做出贡献。接下来将讨论如何向框架中添加更多实用程序来提高我们的工作效率。
云层:这种做法还不够,通常还要写个PO的基类,其中包含对隐式等待、错误截图、日志管理、对象查找的处理,参考代码。
代码语言:javascript复制
/**
* Created by cloudchen on 2017-11-27.
*/
public class PoBase {
public static String url;
public WebDriver wd;
public static int timeout=10;
public PoBase(WebDriver driver)
{
this.wd=driver;
}
public WebElement findelement(By by,int timeout)
{
WebElement element=null;
try {
element = new WebDriverWait(wd, timeout).until(ExpectedConditions.presenceOfElementLocated(by));
}
catch(Exception e)
{
System.out.println(" | " by.toString() " | 对象访问失败");
common.scrshot(wd);
}
finally {
return(element);
}
}
public WebElement findelement(By by)
{
WebElement element=null;
try {
element = new WebDriverWait(wd, 10).until(ExpectedConditions.presenceOfElementLocated(by));
}
catch(Exception e)
{
System.out.println(" | " by.toString() " | 对象访问失败");
common.scrshot(wd);
}
finally {
return(element);
}
}
}
上面的代码只做了截图和对象等待处理,没有做日志处理,大家可以继续使用log4j封装日志。
使用方法页面PO继承PoBase:
代码语言:javascript复制import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
/**
* Created by cloudchen on 2017-11-27.
*/
public class BaiduPo extends PoBase {
public static By baidusearchtext=By.id("kw");
public static By baidusearchbutton=By.id("su");
public static String url="http://www.baidu.com";
public BaiduPo(WebDriver wd)
{
super(wd);
wd.get(this.url);
}
public BaiduPo(WebDriver wd,String url)
{
super(wd);
wd.get(url);
}
public void search(String searchstring)
{
this.findelement(baidusearchtext).sendKeys(searchstring);
this.findelement(baidusearchbutton,5).click();
}
}
测试用例
代码语言:javascript复制/**
* Created by cloudchen on 2017-11-27.
*/
public class PoTest {
WebDriver driver;
@BeforeMethod
public void setup()
{
driver=new ChromeDriver();
}
@Test
public void potest2()
{
BaiduPo bp=new BaiduPo(driver);
bp.search("TestOps");
BaiduPo bp1=new BaiduPo(driver,"http://testops.ke.qq.com");
bp1.search("testops");
}
}
早几年写的代码很多地方可能漏洞百出,请轻喷。。。。
https://github.com/cloudits/selenium3.0
对应文档:
https://www.yuque.com/testops/afopxn/uxorxu
06
选择报告机制
希望我们现在能够快速地扩大自动化测试的数量,并足够频繁地运行它们,以证明前期投资的合理性。当您运行越来越多的测试时,您很快就会发现,如果没有良好的报告机制,理解测试结果将非常困难。
假设我们收到了一个失败的测试。我们如何及时地调查结果,以确定失败是由于AUT错误、AUT上的有意设计更改,还是测试开发和执行期间的错误?
在一天结束的时候,如果我们不能从测试结果中得到有用的见解来采取有意义的纠正行动,那么测试自动化将是无用的。有很多选项可以用来记录自动化测试。Junit和TestNG等测试框架提供的报告机制通常以XML格式生成,可以很容易地由CI/CD工具等其他软件解释。不幸的是,这些xml对于我们人类来说并不容易阅读。
第三方库,如ExtentReport和Allure,可以帮助您创建人类可读的测试结果报告。它们还包括像饼图和屏幕截图这样的视觉效果。
如果您不喜欢这些工具,有一个开源的Java报告库,称为ReportNG。它是TestNG单元测试框架的一个简单HTML插件,它提供了一个简单的、用颜色编码的测试结果视图。而且设置ReportNG很容易。
一个好的报告应该提供详细的信息,例如:通过或失败测试用例的数量、通过率、执行时间,以及测试用例失败的原因。下面的图片是ReportNG生成的示例报告。
云层:现在一般都走Junit体系,然后使用Allure框架美化,而日志最好写一套log4j,然后走influxdb最后grafana可视化。
07
决定如何实现CI/CD
要完成您的Selenium框架,您可能需要处理其他一些需要关注的领域:
- 构建工具和依赖管理器:依赖管理器帮助您管理框架使用的依赖项和库。这些工具的例子包括Maven、Gradle、Ant、NPM和NuGet。在依赖项管理器中投资可以避免在构建框架时丢失依赖项。
- 构建工具可以帮助您构建源代码和依赖库,以及运行测试。下图演示了我们如何使用Maven来执行测试(mvn clean test)。
- 版本控制:所有自动化团队必须相互协作并共享源代码。就像软件开发项目一样,测试和测试工具的源代码存储在源代码控制系统中,也称为版本控制系统。流行的源代码控制系统有GitHub、Bitbucket和TFS。但是,如果您不想与公众共享源代码,我们建议您的团队使用Git建立内部源代码控制系统。
- CI/CD集成:流行的CI系统包括Jenkins、Bamboo和TFS。在对敏捷性需求不断增长的世界中,您很快就会发现,将自动化测试集成到DevOps管道中非常有用,这样您的组织就可以加速交付并保持竞争力。我们推荐Jenkins,因为它是免费的,而且功能强大。
云层:做Mvn非常的好,强烈建议通过Mvn管理,而单元测试本身通过@tag标签分组。
08
将您的框架与其他工具集成
考虑集成以下工具集成到你的框架以增加更多的价值:
- AutoIt是一种类似于基本版的免费脚本语言,设计用于自动化Windows GUI和通用脚本。如果你想使用桌面GUI,比如浏览器的下载对话框,它将帮助你。
- TestRail是一个测试用例管理(TCM)系统,当您的项目有大量测试和相关工作项(如bug和技术任务)时,它被证明是有用的。如果我们的Selenium框架能够在执行后自动将测试结果上传到TestRail,那将是最好的。
- Jira是一个著名的软件开发和测试生态系统。因此,可以考虑在一些常见场景中集成Jira,比如根据Selenium测试结果自动发布和关闭Jira bug。
云层:AutoIt整合比较好处理一些窗体,别的例如Jira、禅道或者influxdb之类的整合最好还是写个http请求调用吧,不要考虑通过UI模式来完成整合。而PO对象模式也可以通过配置的方式来实现,从而让前端研发改变了元素可以实现自动同步页面属性的地步,再可以做一个自动全局回放扫描分析错误的功能,Junit断言中断捕获,最后给报告的体系,来快速了解哪些对象失败。
09
结论
Selenium是执行功能和回归测试的强大工具。为了获得最大的收益,我们应该从一开始就有一个好的框架架构。一旦你夯实了一个坚实的基础,你在上面建造的任何东西都会留下来。
云层:UI虽好但是也是一个很笨重的测试,在规范的前端下可以方便的进行回归测试,但是从敏捷下的持续测试来说,UI自动化隔离和效率都不够好,所以有效把测试下层更有意义,在未来可能连接口测试都要消失了!
ps.现在云层已经更加喜欢controller层的接口测试了,UI可能离我越来越远了吧。