TestNg的并发有Method、Class、Method级别提供测试时使用,而并发最终在Selenium中目的肯定不是为了性能测试(千万不要乱使用工具去发挥它的弱点),是为了解决执行庞大测试用例时的执行效率,TestNg是如此、Selenium Grid也是如此。那TestNg与Selenium Grid结合能干什么呢?来看下面对话:
TestNg:我的并发能力很强大,可以同时启动多线程执行;
Selenium2:我的Selenium Grid组件使用集群能力,可以让多台机器任我分配执行;
Teacher(老师):现在有两台配置一般的机器,需要两台机器同时运行2个用例,这样每次都能执行4个用例来提高效率;
TestNg:我不需要两台机器,一台运行4个测试用例就简单搞定;
Teacher(老师):但是机器配置不够高,同时运行4个浏览器会出现卡顿;Selenium Grid你可以执行吗?
Selenium2:我需要4台机器通过Selenium Grid组件才能同时运行4个用例;
Teacher(老师):我有个好主意,你们结合执行就好了。
从上面的对话分析Selenium Grid解决的是多台机器运行测试用例,而不是一台机器同时运行多个用例;当然Selenium Grid的优点在多台机器上配合不同浏览器测试兼容性是完美的提高了测试的效率,这点是值得肯定的。而想要的一台机器启动2个浏览器甚至多个浏览器同时执行多个测试用例,这点单独的Selenium Grid是不支持的;而TestNg呢?它的缺点就是不能控制机器任它分配;所以两者结合成为了最优解决方案。
1测试代码:
public class NewTest {
WebDriver driver = null;
@BeforeMethod
public void beforeMethod(){
driver = new ChromeDriver();
}
@AfterMethod
public void afterMethod(){
driver.quit();
}
@Test()
public void a() {
driver.get("http://www.baidu.com");
driver.findElement(By.id("kw")).sendKeys("软件测试");
driver.findElement(By.id("su")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Assert.assertEquals(driver.getTitle().contains("软件测试"),true);
}
@Test()
public void b() {
driver.get("http://www.baidu.com");
driver.findElement(By.id("kw")).sendKeys("selenium");
driver.findElement(By.id("su")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Assert.assertEquals(driver.getTitle().contains("selenium"),true);
}
}
2TestNg.xml配置
如上代码,使用@Test注解的两个方法,xml配置了并发级别为methods;运行时两个@Test注解方法同时执行,会在client同时启动两个浏览器,然后执行代码;但是出现问题了,就是会有一个chromedriver.exe客户端的socket服务不能执行关闭。而带来这个问题的原因就是并发引起的线程安全问题。
先来看@BeforeMethod注解的方法,当多线程并发运行的时候,会同时执行这个方法,也就是说会new两个对象,假如是test1,test2两个对象,而driver这个对象引用,一开始引用的是test1对象,但是线程同时启动,driver又引用了test2这个对象,这时候test1对象以及被JVM的垃圾回收了;这时候就出现问题,所有的操作会出现在driver引用的test2中操作,而@AfterMethod执行的quit关闭方法,只是关闭了test2对象的socket服务进程以及启动的浏览器,而test1对象打开的socket服务进程以及启动的浏览器就会一直停留在系统中,没有被关闭。
好像看上面的解释有点晕,还得介绍下JVM内存分配的原理才能说的清楚。JVM内存主要分为三块:栈、堆、静态区(方法区)
l 栈:执行速度快,一般的对象引用存放在栈中
l 堆:执行速度较栈慢,空间大,new的对象存放在堆中
l 静态区(方法区):存储固定位置,静态变量、静态方法存放在静态区
来通过图说明还是清晰很多,
首先WebDriver driver,driver称为对象引用,放在栈内存中,当使用driver时引用到堆中new的对象。new ChromeDriver称为实例化对象,new实例化的对象都放在堆内存。
说明这两点后,再来根据上面问题的解释来翻译一下。首先同个线程并发实例化对象test1、test2;当实例化对象test1的时候,driver引用的是test1,而实例化对象test2后,driver引用变成了test2,这时候test1被JVM回收。在内存中绝对不会说同时实例化对象,注意是内存中;只是这个前后的时间可以不计,但也是存在这个时间间隔的。
3.解决办法:
(1)对象实例局部化,代码如下:
@Test()
public void f() {
WebDriver driver = new ChromeDriver();
driver.get("http://www.baidu.com");
driver.findElement(By.id("kw")).sendKeys("软件测试");
driver.findElement(By.id("su")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Assert.assertEquals(driver.getTitle().contains("软件测试"),true);
}
@Test()
public void d() {
WebDriver driver = new ChromeDriver();
driver.get("http://www.baidu.com");
driver.findElement(By.id("kw")).sendKeys("selenium");
driver.findElement(By.id("su")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Assert.assertEquals(driver.getTitle().contains("selenium"),true);
}
对象引用局部化,这时候注意了,虽然driver的名字是一样的,但是在栈中的内存物理地址是不一样了,两个同名的driver对象引用,引用了不同的两个对象,这样就不会出现引用的变化,运行正常进行。
(2)使用ThreadLocal
ThreadLocal 主要用来提供线程局部变量,也就是变量只对当前线程可见;存放的值是线程内独享的,线程间互斥的;概念都是模糊的,下面来实例。
public class NewTest {
public static ThreadLocal ThreadDriver=new ThreadLocal() ;
@BeforeMethod
public void beforeMethod(){
WebDriver driver = new ChromeDriver();
ThreadDriver.set(driver);
}
@AfterMethod
public void afterMethod(){
ThreadDriver.get().quit();
ThreadDriver.remove();
}
@Test()
public void f() {
ThreadDriver.get().get("http://www.baidu.com");
ThreadDriver.get().findElement(By.id("kw")).sendKeys("软件测试");
ThreadDriver.get().findElement(By.id("su")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Assert.assertEquals(ThreadDriver.get().getTitle().contains("软件测试"),true);
}
@Test()
public void d() {
ThreadDriver.get().get("http://www.baidu.com");
ThreadDriver.get().findElement(By.id("kw")).sendKeys("selenium");
ThreadDriver.get().findElement(By.id("su")).click();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Assert.assertEquals(ThreadDriver.get().getTitle().contains("selenium"),true);
}
}
通过上面代码来分析一下
l 先创建ThreadLocal,泛型是WebDriver可以看到ThreadLocal接下来的对象引用肯定是WebDriver类型;
l 然后在@BeforeMethod注解里new实例化对象,等同把new对象放在局部了;new对象test1、test2存在堆内存;而两个driver对象引用存在栈内存;
l ThreadLocal.set()把对象引用放入到了ThreadLocal里面,当我们使用ThreadLocal.get()取出对象引用时,栈内存通过ThreadLocal存好的对象引用找到了堆内存中的对象。
整个过程有点绕,还是需要慢慢理解;可能会有人问,ThreadLocal会不会找错线程啊?答案是想多了啊,因为java最小执行单元就是线程;而多线程并发在ThreadLocal中资源是不共享的,是互斥的。
TestNg与Selenium WebDriver的整合告一段落;上面代码实例化对象通过调用服务的方式就可以使用Selenium Grid来分配并发执行;在两种方法解决并发方法中更支持使用ThreadLocal。