总的来说,我同意:
如果已经使用像JUnit这样的框架,则手动处理线程可能是一个不好的主意。
但是,看着你提到的Parallelized
运行器和JUnit 4.12中@Parametrized
的内部实现,这是可能的。
每个测试用例都被安排执行。默认情况下,JUnit在单个线程中执行测试用例。 Parallelized
扩展了Parametrized
,以使单线程测试调度程序替换为多线程调度程序,因此,要了解这如何影响运行Parametrized
测试用例的方式,我们必须查看JUnit Parametrized
源代码:
https://github.com/junit-team/junit/blob/r4.12/src/main/java/org/junit/runners/Parameterized.java#L303
看起来像:
@Parametrized
测试用例被拆分成一组 TestWithParameters
,每个测试参数一个
- 为每个
TestWithParameters
实例创建并安排执行的 Runner
(在这种情况下,Runner
实例是专门的 BlockJUnit4ClassRunnerWithParameters
)
实际上,每个 @Parametrized 测试用例都会生成一组要运行的测试实例(每个参数一个实例),每个实例都独立地安排执行,因此在我们的情况下(使用 WebDriver
实例作为参数的 Parallelized
和 @Parametrized
测试),多个独立的测试将在每个 WebDriver
类型的专用线程中执行。这很重要,因为它允许我们在当前线程的范围内存储特定的 WebDriver
实例。
请记住,这种行为依赖于 junit 4.12 的内部实现细节,可能会发生变化(例如,请参见 RunnerScheduler
中的注释)。
请看下面的示例。它依赖于提到的JUnit行为,并使用ThreadLocal存储在同一测试用例组中共享的WebDriver实例。唯一的技巧是仅在@Before中初始化ThreadLocal,并销毁每个创建的实例(在@AfterClass中)。
package example.junit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
@RunWith(Parallelized.class)
public class ParallelSeleniumTest {
enum WebDriverType {
CHROME,
FIREFOX
}
static class WebDriverFactory {
static WebDriver create(WebDriverType type) {
WebDriver driver;
switch (type) {
case FIREFOX:
driver = new FirefoxDriver();
break;
case CHROME:
driver = new ChromeDriver();
break;
default:
throw new IllegalStateException();
}
log(driver, "created");
return driver;
}
}
@Parameterized.Parameter
public WebDriverType currentDriverType;
@Parameterized.Parameters(name= "{0}")
public static Collection<Object[]> driverTypes() {
return Arrays.asList(new Object[][] {
{ WebDriverType.CHROME },
{ WebDriverType.FIREFOX }
});
}
private static ThreadLocal<WebDriver> currentDriver = new ThreadLocal<WebDriver>();
private static List<WebDriver> driversToCleanup = Collections.synchronizedList(new ArrayList<WebDriver>());
@BeforeClass
public static void initChromeVariables() {
System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");
}
@Before
public void driverInit() {
if (currentDriver.get()==null) {
WebDriver driver = WebDriverFactory.create(currentDriverType);
driversToCleanup.add(driver);
currentDriver.set(driver);
}
}
private WebDriver getDriver() {
return currentDriver.get();
}
@Test
public void searchForChromeDriver() throws InterruptedException {
openAndSearch(getDriver(), "chromedriver");
}
@Test
public void searchForJunit() throws InterruptedException {
openAndSearch(getDriver(), "junit");
}
@Test
public void searchForStackoverflow() throws InterruptedException {
openAndSearch(getDriver(), "stackoverflow");
}
private void openAndSearch(WebDriver driver, String phraseToSearch) throws InterruptedException {
log(driver, "search for: "+phraseToSearch);
driver.get("http://www.google.com");
WebElement searchBox = driver.findElement(By.name("q"));
searchBox.sendKeys(phraseToSearch);
searchBox.submit();
Thread.sleep(3000);
}
@AfterClass
public static void driverCleanup() {
Iterator<WebDriver> iterator = driversToCleanup.iterator();
while (iterator.hasNext()) {
WebDriver driver = iterator.next();
log(driver, "about to quit");
driver.quit();
iterator.remove();
}
}
private static void log(WebDriver driver, String message) {
String driverShortName = StringUtils.substringAfterLast(driver.getClass().getName(), ".");
System.out.println(String.format("%15s, %15s: %s", Thread.currentThread().getName(), driverShortName, message));
}
}
它将打开两个浏览器窗口,并在每个窗口中同时执行三个测试用例。
控制台会打印出类似以下的内容:
pool-1-thread-1, ChromeDriver: created
pool-1-thread-1, ChromeDriver: search for: stackoverflow
pool-1-thread-2, FirefoxDriver: created
pool-1-thread-2, FirefoxDriver: search for: stackoverflow
pool-1-thread-1, ChromeDriver: search for: junit
pool-1-thread-2, FirefoxDriver: search for: junit
pool-1-thread-1, ChromeDriver: search for: chromedriver
pool-1-thread-2, FirefoxDriver: search for: chromedriver
main, ChromeDriver: about to quit
main, FirefoxDriver: about to quit
你可以看到,驱动程序是为每个工作线程创建一次并在结束时销毁。
总之,在执行线程的上下文中,我们需要类似于
@BeforeParameter
和
@AfterParameter
的东西,快速搜索显示这样的想法已经在Junit中注册为
问题。
WebDriver myWebDriver;
也作为成员变量。然后,在@Before中,我执行myWebDriver = sWevDriver.get();
并在其余代码中使用myWebDriver。这样做的效果是只打开了2个WebDriver实例,并且测试使用了正确的实例。但是,在@AfterClass中,我定义了sWebDriver.get().quit()
,这似乎仅适用于两个实例中的一个。 - Mercious