使用 Selenium Grid2 来提高全球化测试中自动化截图

      2020-08-01 20:56      JAVA开发
-->

在持续交付模式下的全球化测试对自动化测试的挑战

挑战

在 2014 年 3 月, 我们所测试的产品正式进入 ContinuousDelivery 的开发模式,从每三个月发布一次产品转变为每个月发布一次产品。这很大程度上压缩了我们用来准备和执行全球化测试的时间,对于项目组测试成员的技术能力和对产品熟悉程度是一种挑战。我们在 TVT 准备过程中,需要在短时间内,将不同语言的界面截图归档保存并发送给翻译验证测试人员。过多的手动操作使我们很难专注于分析和发现全球化问题,项目组考虑采用自动化截图来取代原有的操作。

改进

在我们全面采用 Selenium WebDriver 进行自动化截图后,对结果不太满意。一方面, 受制于测试机器的性能,我们无法在一台测试机器上并行执行多语言的自动化截图;另一方面,曾经有翻译验证测试人员向我们反映在截图中字体存在问题,解决方法是需要安装系统语言包并且将系统语言切换成特定语言的操作系统重新进行截图。我们决定采用 Selenium 套件中的 Grid2 将测试分布到特定测试机器中进行测试。

Selenium Grid简介

Selenium-Grid 允许你在不同的测试机器上同时执行相同的测试。从全球化测试的角度可以理解为将不同语言的测试分布在不同的测试机器上运行。这样测试的好处在于可以得到最符合语言环境的截图,同时还能减少执行时间。

Selenium-Grid 同样支持在不同的浏览器环境上执行相同的测试。比如说,我们分别在三台机器上安装 IE,Firefox,和 Chrome,然后将测试分布在三台机器上同时执行,就可以测试软件在不同浏览器上的全球化支持情况。

Selenium-Grid 分为 1.0 和 2.0 个版本。1.0 提供对 SeleniumRC 的支持,在一些遗留系统中可能还是以 1.0 版本为主;2.0 版本则对目前的 WebDriver 提供支持,在本文中我们将重点针对 Selenium-Grid2.0 进行介绍。本文中开发环境采用 Java 语言,浏览器以 Firefox 为主。整个测试过程将不采用 Junit 或者 TestNG 测试框架,而是采用 Java 代码实现多线程测试环境。

使用 Selenium WebDriver 进行自动化截图的实现

启动 Selenium Grid2

通过以下命令启动 Selenium Grid2 控制器。控制器可以接受所有的控制请求并将测试分布到合适的 Grid 节点。为防止 Java内存溢出,可以设置虚拟内存的用量。

清单 1. 启动控制器
java -Xms1600m -Xmx1600m -jar "C:\dev\selenium-server-standalone-2.42.1.jar" -role hub

通过以下命令启动 SeleniumGrid2 节点,启动 Grid 节点时需要告知控制器的位置。在全球化测试中,一个节点可以理解为一个独立语言版本的操作系统,上面安装有独立语言版本的 Firefox 浏览器。

清单 2. 启动 Grid 节点
java -jar "C:\dev\selenium-server-standalone-2.42.1.jar" -role node -hub,http://SXV2V668:4444/grid/register -port 5556

当我们需要针对 14 个国家不同语言进行全球化测试时,我们可以将对应语言环境的操作系统作为节点连接到 SeleniumGrid2。当运行自动化测试代码时,不同节点上的浏览器会被切换成符合语言环境的设置,并同步开始对需要测试的软件执行测试代码,从而提高执行效率。SeleniumGrid2 的构架图如下,首先需要启动 Grid 控制器,然后启动多个指向该控制节点的远程 Grid 节点。

图 1. Selenium Grid 构架图

当测试程序运行时,测试代码会被并发的发送给控制器,然后由控制器决定发送到指定的控制节点执行测试。当某个节点无法获取的情况, 测试程序需要告知控制器可以使用的备用节点。

图 2. Selenium Grid2 测试程序序列图

实现在 Grid2 中执行并行测试

    在 Eclipse 中创建一个 Maven 项目,并将 Selenium 的支持包添加到 pom.xml。由于 Maven 自身的特性,添加成功的依赖包会自动从 MavenRepository 获取相关的 Jar 包并添加到项目的库中。
图 3. 在 Eclipse 图形界面添加正确的依赖包
    成功的添加依赖包后可以在项目目录中查看所有导入的 Java 库,其中 MavenDependencies 中可以看到 Selenium-*.jar 的库文件。这里值得一提的是,在实际测试中,我们发现旧版本 Selenium 库有可能和浏览器发生不兼容的现象,因此请尽量保证这里的 Selenium 库在 2.4.0 以上。
图片 4. Eclipse Project 的项目结构
    创建一个 Java 类,名称为 geniueTestCase.java,并实现 Runnable 接口。我们可以将平时使用的 FirefoxDriver 相关的测试代码导入到 geniueTestCase.java 文件中。
图片 5. 创建 geniueTestCase.java
    需要注意的是 FirefoxDriver 不支持远程运行,我们需要对代码进行修改。我们首先需要创建DesiredCapabilities对象来定义浏览器版本和操作系统,然后在申明RemoteWebDriver实例时将DesiredCapabilities对象传递给构造函数的变量。
清单 3. 实例化 RemoteWebDriver
private void init() throws MalformedURLException {if (driver == null) {DesiredCapabilities dc =DesiredCapabilities.firefox();dc.setCapability(FirefoxDriver.PROFILE, profile);try{synchronized (RemoteWebDriver.class){driver = new RemoteWebDriver(new URL(nodeUrl), dc);}}catch(org.openqa.selenium.remote.UnreachableBrowserException e){System.out.println(nodeUrl+ "无法连接, 切换到备份节点");driver = null; this.nodeUrl = SystemConstant.LangCode.EN.getNodeURL();init();}catch(org.openqa.selenium.WebDriverException e){System.out.println(nodeUrl+ "异常, 切换到备份节点");driver = null;this.nodeUrl = SystemConstant.LangCode.EN.getNodeURL();init();}}}
    将现有的 SeleniumGrid2 节点和对应的语言环境放在枚举实例中,这样在自动化测试过运行某个特定语言环境的测试代码时,就可以很容易获取指定节点,同时也提高了代码的可读性。
清单 4.LangCode 枚举类
public class SystemConstant{ public enum LangCode{CN("zh-cn", "http://SXV2V668:5557/wd/hub"), TW("zh-tw","http://SXV2V670:5557/wd/hub"), DE("de-de","http://SXV2V664:5557/wd/hub"), CS("cs","http://SXV2V672:5557/wd/hub"), ES("es-es", "http://SXV2V669:5557/wd/hub"), RU("ru","http://SXV2V675:5557/wd/hub"), JP("ja","http://SXV2V666:5557/wd/hub"), KO("ko","http://SXV2V667:5557/wd/hub"), HU("hu","http://SXV2V673:5557/wd/hub"), PL("pl","http://SXV2V674:5557/wd/hub"), TH("th","http://SXV2V671:5557/wd/hub"), IT("it-ch","http://SXV2V665:5557/wd/hub"), PTBR("pt-br","http://SXV2V662:5557/wd/hub"),FR("fr","http://SXV2V663:5557/wd/hub");private String locale, nodeURL;LangCode(String locale, String nodeURL) { this.locale = locale; this.nodeURL = nodeURL;}public String getLocale() {return locale;     }public void setLocale(String locale) {this.locale = locale;    }public String getNodeURL() {return nodeURL;    }public void setNodeURL(String nodeURL) {this.nodeURL = nodeURL;    } @Overridepublic String toString() {return locale + " at " + nodeURL;    }  }}
    在 main 中添加以下代码就可以让 TestCase 在简中、繁中、日语的环境上并行测试。由于 geniueTestCase 本身通过实现 Runnable 接口来创建线程,增加了程序的健壮性,代码可以被多个线程共享,同时线程中代码和数据有效分离,提高了代码的可维护性。在实际运行中,RemoteWebDriver 的实例化会消耗比较多的 CPU 时间, 多个线程同时初始化程序也可能会抛出异常。然而我们可以将 RemoteWebDriver 的实例化包含在 synchronized(RemoteWebDriver.class) 代码同步块中,以保证在同一时刻只有一个线程可以对 RemoteWebDriver 进行实例化,但是实际测试运行的顺序和线程启动的顺序可能还是会有些差别。
清单 5.在多个 Grid 节点执行远程测试
public static void main(String[] args) throws MalformedURLException,InterruptedException {List<geniueTestCase> testList = new ArrayList<geniueTestCase>();String url = "http://9.110.79.148:8080";String hubUrl = "http://9.115.47.65:4444/wd/hub";testList.add(new geniueTestCase("C:\\dev\\firefox", url,SystemConstant.LangCode.EN.getNodeURL(),hubUrl,SystemConstant.LangCode.EN.getLocale()));testList.add(new geniueTestCase("C:\\dev\\firefox", url,SystemConstant.LangCode.CN.getNodeURL(),hubUrl,SystemConstant.LangCode.CN.getLocale()));testList.add(new geniueTestCase("C:\\dev\\firefox", url,SystemConstant.LangCode.TW.getNodeURL(),hubUrl,SystemConstant.LangCode.TW.getLocale()));testList.add(new geniueTestCase("C:\\dev\\firefox", url,SystemConstant.LangCode.JP.getNodeURL(),hubUrl,SystemConstant.LangCode.JP.getLocale()));for (geniueTestCase e : testList){new Thread(e).start();}}

结束语

对于我们全球化测试部门来说,拥有一套符合产品特点的自动化测试框架有百益而无一害。从我们 2014 年 4 月份开始使用自动化截图到现在使用 Grid2 来进行并行自动化截图,我们的执行效率相对于原来提升了 2 倍,我们也帮助产品组在一年内成功的交付了支持全球化的 15 个版本的软件产品。

如果大家感兴趣,可以参考本文的代码下载,尝试将自己的自动化测试代码改写成拥有远程并行测试的强大功能。