正如 Dmitri T 在他的回答中指出的那样,当使用一些静态字段时,通过 Jmerter 运行 JUnit-Selenium 测试的多线程会中断。
在我们的项目中,我们遇到了与问题状态相同的症状,例如测试找不到元素,或者当其他人正在使用它时一个线程关闭了webdriver。
因此我们实现了以下方法,这使我们的测试踏步安全并允许通过 JMeter 以多线程方式运行。
首先,让我们提一下,Junit-Selenium 测试中的静态对象通常与@BeforeClass 方法一起出现,这些方法必须声明为静态并使用静态字段。您也可以使用@ClassRule 注释,该注释也必须放在静态字段上。不过@ClassRule注解并没有被JMeter Junit Sampler处理,所以我就不多说了。
在我们的例子中使用@BeforeClass 初始化,我们有TestBase 类,我们在其中初始化webdriver 和一些其他资源以用于所有测试。我只是简化了我们的真实类以进行更清晰的演示:
public abstract class P01_SL_TestBase {
public static WebDriverResource driver = new FirefoxDriverResource();
public static PageProvider<ARMLoginPage> loginPageProvider;
public static ARMLoginPage loginPage;
public static ARMApplicationStartPage ARMPage;
@BeforeClass
public static void loadApplication () {
initResourcesForJmeterRunner();
loadARM (armUser);
}
public static void initResourcesForJmeterRunner() throws Throwable {
webDriverResource.before();
}
public static void loadARM(BPMUser armUser) {
loginPageProvider =
new PageProvider<ARMLoginPage>(ARMLoginPage.class,
BASE_URL_ADF_ARM_LOGIN_PAGE,
driver.getDriver()); //Here
//we get thread-local value of
//Firefox driver for our current thread
//from the threadwide storage
loginPage = loginPageProvider.goHome();
}
}
正如您在TestBase 中看到的,我们有静态字段驱动程序和其他一些常见情况。还有@BeforeClass 方法可以做两件事。首先,webdriver 被实例化。其次,初始化要测试的应用程序。所以TestBase 类中的所有内容都与在一个线程或多线程中运行的测试无关。
多线程运行的实现我们放到WebDriverResource类中:
public abstract class WebDriverResource extends ExternalResource {
// private final RemoteWebDriver driver; //this is implementation for
//one thread running,
//it is not thread-safety
/**
* {@code threadLocalDriverStorage} field stores thread-local instances
* of {@code RemoteWebDriver}.
* Note that now {@code threadLocalDriverStorage} is not static. We made
* this for backward compatibility of some
* existent tests using non-static createDriver() method.
* TODO refactor createDriver() declaration and implementation to static
* variant.
*/
private ThreadLocal<RemoteWebDriver> threadLocalDriverStorage =
new ThreadLocal<RemoteWebDriver>() {
@Override
protected RemoteWebDriver initialValue() {
return createDriver(locale.toLanguageTag());
}
};
public WebDriverResource() {
}
protected abstract RemoteWebDriver createDriver(String language);
@Override
public void before() throws Throwable {
//here we call threadLocalDriverStorage.get() first time,
//and so the get() will perform initialValue()
//and save to storage thread-local value for the current thread
threadLocalDriverStorage.get()
.manage()
.window()
.maximize();
}
@Override
protected void after() {
logger.fine("quit browser...");
//driver.quit();
threadLocalDriverStorage.get().close();
}
/**
* @return thread-local value of {@code RemoteWebDriver}
*/
public RemoteWebDriver getDriver() {
return threadLocalDriverStorage.get();
}
}
这里我们使用TreadLocal类来实现threadLocalDriverStorage字段。我们重写initialValue() 方法以返回RemoteWebDriver 对象的实例。
然后在before() 方法中,当我们第一次调用TreadLocal.get() 时,它会导致调用initialValue() 方法并为当前线程实例化RemoteWebDriver 对象。
还有getDriver(),它返回胎面局部值RemoteWebDriver。应该在所有测试类中使用此方法来获取驱动程序对象。
我们还将 webdriver 字段存储在页面对象模型类中。因此,我们还将受保护的静态 ThreadLocal<RemoteWebDriver> threadLocalDriverStorage 字段添加到我们的基本页面类中。但是这里我们不覆盖initialValue() 方法。相反,我们使用TreadLocal.set() 方法来保存当前线程的值。
public abstract class Page {
// private final RemoteWebDriver driver; //this is implementation for
//one thread running,
//is not thread-safety
/**
* {@threadLocalDriverStorage} field stores thread-local instances of
* {@RemoteWebDriver}.
*
* Note that {@threadLocalDriverStorage} must be static. Without that
* static classes extended Page will
* work with unpredictable instance of {@threadLocalDriverStorage} and
* as result would not set and get {@RemoteWebDriver} instance properly
*
* Note that {@ThreadLocal.initialValue()} method is not overrided.
* Therefore thread-local value must be stored by
* {@ThreadLocal.set()} method.
* We do that in class consractor.
*/
protected static ThreadLocal<RemoteWebDriver> threadLocalDriverStorage =
new ThreadLocal<RemoteWebDriver>();
public Page(WebDriver driver) {
super();
threadLocalDriverStorage.set((RemoteWebDriver)driver);
}
}