Java+TestNG+Appium实现单机多个Android终端并发测试
前⾔
我们知道,单台 PC 上⽤ Appium 连接多个 Android 终端进⾏测试时,需要同时⽤不同的端⼝号启动不同的 Appium Server,例如启动两个服务器:
node main.js -p4723-bp4724-chromedriver-port9515-U emulator1
node main.js -p4725-bp4726-chromedriver-port9516-U emulator2
然后测试代码的 AppiumDriver 连接到对应的端⼝,然后就可以并发地执⾏测试。
这就意味着有很多环境数据需要配置,包括各种端⼝号,终端的UDID等,同时也需要为不同的终端分发测试⽤例,这些数据最好是与 Java 测试代码分离。另外服务器的启动和关闭最好也能通过代码⾃动执⾏。
之前我⽤的是 Junit4 单元测试框架,感觉功能不够强⼤,难以实现上述要求,后来了解了下 TestNG 框架,它⾃带的 xml ⽅式,参数化测试,并发执⾏等功能,刚好可以⽤来实现我设想的功能。
完整代码地址: ,下⾯就来介绍⼀下思路。
效果展⽰
⾸先看下最终实现的效果,只需配置好测试套的 xml ⽂件,将环境信息作为参数,指定好要执⾏的⽤例,执⾏ RunSuite 类,即可完成从启动服务器开始的所有测试过程。
1. 配置测试套 xml ⽂件,每个xml对应⼀个终端:
环境参数通过 parameter 标签配置,其中包含:
node: 的路径,如果配置了系统环境变量,直接填 node 就⾏。
appium.js:appium.js的路径,新版本的Appium应该是main.js的路径。和node参数配合,⽤来执⾏启动Appium 服务器。
port、bootstrap_port、chromedriver_port:Appium服务器的端⼝。
udid:终端名称,可以通过 adb devices 查到。
剩下的参数为 DesiredCapabilities 需要的参数。
第⼀个终端 l:
<suite name="WebViewSuit1" >
<parameter name="suitName"value="WebViewSuit1"/>
<parameter name="node"value="node"/>
<parameter name="appium.js"value="C:\Users\chenyu\AppData\Local\Programs\appium-desktop\resources\app\node_modules\appium\build\lib\main.js <parameter name="port"value="4725"/>
<parameter name="bootstrap_port"value="4726"/>
<parameter name="chromedriver_port"value="9516"/>
<parameter name="udid"value="127.0.0.1:21503"/>
<parameter name="platformName"value="Android"/>
<parameter name="platformVersion"value="4.4.4"/>
<parameter name="deviceName"value="127.0.0.1:21503"/>
<parameter name="appPackage"value="demo"/>
<parameter name="appActivity"value=".MainActivity"/>
<parameter name="noReset"value="false"/>
<parameter name="app"value="demo-debug-v1.2.apk"/>
<test name="WebView">
<classes>
<class name="st.TestWebView"/>
</classes>
</test>
<test name="Animation">
<classes>
<class name="st.TestAnimation"/>
</classes>
</test>
</suite>
第⼆个终端 l:
<suite name="WebViewSuit2" >
<parameter name="suitName"value="WebViewSuit2"/>
<parameter name="node"value="node"/>
<parameter name="appium.js"value="C:\Users\chenyu\AppData\Local\Programs\appium-desktop\resources\app\node_modules\appium\build\lib\main.js <parameter name="port"value="4723"/>
<parameter name="bootstrap_port"value="4724"/>
<parameter name="chromedriver_port"value="9515"/>
<parameter name="udid"value="emulator-5554"/>
<parameter name="platformName"value="Android"/>
<parameter name="platformVersion"value="6.0"/>
<parameter name="deviceName"value="emulator-5554"/>
<parameter name="appPackage"value="demo"/>
<parameter name="appActivity"value=".MainActivity"/>
<parameter name="noReset"value="false"/>
<parameter name="app"value="demo-debug-v1.2.apk"/>
<test name="WebView">
<classes>
<class name="st.TestWebView"/>
</classes>
</test>
<test name="Animation">
<classes>
<class name="st.TestAnimation"/>
</classes>
</test>
</suite>
连接好两个Android终端(模拟器或者真机),运⾏RunSuite 类:
即可启动并发测试,⾃动启动两个Appium服务器,⾃动执⾏完测试套,之后停⽌服务器:
实现原理
1. RunSuite 类
package main.java;
stng.TestListenerAdapter;
stng.TestNG;
import java.util.ArrayList;
import java.util.List;
public class RunSuite {
public static void main(String[] args) {
TestListenerAdapter tla = new TestListenerAdapter();
TestNG testng = new TestNG();
List<String> testFieldList = new ArrayList<>();
/
/testFieldList.add("l");
testFieldList.add("l");
testFieldList.add("l");
testng.setTestSuites(testFieldList);
testng.addListener(tla);
testng.setSuiteThreadPoolSize(2);
testng.run();
System.out.println("ConfigurationFailures: "+ConfigurationFailures());
System.out.println("FailedTests: " + FailedTests());
}
}
这个类⽐较简单,做的主要是加载了 TestSuite 的 xml ⽂件,然后并发执⾏多个测试套,⽤到的都是TestNG⾃带的库。
加载 xml 有两种⽅式,⼀是单独添加各个 TestSuite 的 xml ⽂件:
testFieldList.add("l");
testFieldList.add("l");
或者先创建⼀个汇总的 xml ⽂件,把各个TestSuite放到其中,在加载⼀次汇总的⽂件即可。
l :
<suite name="Main suite">
<suite-files>
<suite-file path="l"/>
<suite-file path="l"/>
</suite-files>
</suite>
RunSuite.java 中:
testFieldList.add("l");
另外要注意设置线程池⼤⼩,不设的话只有⼀个线程,就不会并发执⾏了,这⾥有两个 TestSuite,可以设置成2:testng.setSuiteThreadPoolSize(2);
2. AppiumTestCase 类
AppiumTestCase 将作为所有TestCase的基类,其中包含了 Appium 服务器的启动和停⽌,以及 AppiumDriver 的连接和退出。
2.1 Appium Server 的启动和停⽌
因为⼀个 TestSuite 对应⼀个终端,⼀个终端对应⼀个Server,因此Server只需要在每个 TestSuite 开始时启动,在 TestSuite 结束时停⽌。于是这⾥就⽤到了 TestNG 的 @BeforeSuite 和 @AfterSuite 注解。
Server 启动函数:
@Parameters({"node", "appium.js", "port", "bootstrap_port", "chromedriver_port","udid"})
@BeforeSuite
public void startServer(String nodePath, String appiumPath, String port,String bootstrapPort, String chromeDriverPort, String udid) {
boolean needStartServer = true;
if (needStartServer) {
new Thread(new Runnable() {
@Override
public void run() {
try { Instance().startServer(nodePath, appiumPath, port, bootstrapPort, chromeDriverPort, udid);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
开头⽤到了 @Parameters 注解,在这个注解中引⽤在 xml ⽂件中配置的 parameter 标签,读取的值作为startServer(…)函数的输⼊参数。
使⽤ @BeforeSuite 注解,指定此函数在整个 TestSuite 开始时执⾏⼀次。
创建⼀个新线程来执⾏启动 Server 的任务,调⽤的是 Instance().startServer() 函数,并传⼊通过@Parameters 注
java模拟器安卓解⽅式获取的 xml 参数值,AppiumServerController 类后⾯再讲。
这⾥⼀定要启动⼀个新线程,因为测试期间 Server ⼀直在后台运⾏,会阻塞线程,如果和测试代码放到⼀个线程⾥,那测试将⽆法进⾏下去。
执⾏启动 Server 的代码后,等待了 20s,给 Server ⾜够的时间来启动。然⽽这不是⼀个好的做法,正确的做法应该是读取 Server 进程
的输⼊流,当出现 Server 成功启动的信息后,再执⾏后⾯的测试,这个后⾯再优化。
更新,使⽤锁机制保证⽤例在server启动后执⾏:
在启动 server的⼦线程开始阶段获取锁:
protected ReentrantLock serverLock = new ReentrantLock();
serverLock.lock();
当检测到命令⾏输出服务启动成功的信息后,释放锁
while ((line = adLine()) != null) {
System.out.println(line);
if(line.startsWith("[Appium] Appium REST http interface listener started on")) {
lock.unlock();
}
}
在主线程中,先延时2秒,再尝试获取锁,这样在server启动前,主线程会处于阻塞状态,server启动获取锁后,主线程才继续执⾏
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论