背景:现在使用Java JUnit4,愿意迁移到JUnit5或TestNG。
当前状态:进行了100多次硒测试。它们中的大多数通过Junit4中的@RunWith(Parameterized.class)进行重复。 (即,根据所提供的一组参数(通常是浏览器类型+用户身份的组合)创建测试类的多个实例。)共享一组有限的大约12个用户。
限制:经过测试的应用程序可防止同一用户同时在多个位置登录。因此,如果用户在一个线程中运行的某个测试中登录应用程序,则会导致同一用户同时在另一个线程中运行的另一个测试中立即注销。
问题:当并行执行的测试无法共享某些资源时,是否有建议的方法来管理线程安全? 还是如何在相同的线程中执行使用相同资源执行的那些测试?
感谢创意。
这是到目前为止我在TestNG上发现的一些解决方案的简化示例...:
public abstract class BaseTestCase {
protected static ThreadLocal<WebDriver> threadLocalDriver = new ThreadLocal<>();
protected String testUserName;
private static final Set<String> inUse = new HashSet<>();
public BaseTestCase(WebDriver driver, String testUserName) {
threadLocalDriver.set(driver);
this.testUserName = testUserName;
}
private boolean syncedAddUse(@NotNull String key){
synchronized (inUse){
return inUse.add(key);
}
}
private boolean syncedRemoveUse(@NotNull String key){
synchronized (inUse) {
return inUse.remove(key);
}
}
@DataProvider(parallel = true)
public static Object[][] provideTestData() {
//load pairs WebDriver+user from config file. E.g.:
//Chrome + chromeUser
//Chrome + chromeAdmin
//Firefox + firefoxUser
//etc...
}
@BeforeMethod
public void syncPoint() throws InterruptedException {
while( !syncedAddUse(testUserName) ){
//Waiting due the testUserName is already in use at the moment.
Thread.sleep(1000);
}
}
@AfterMethod
public void leaveSyncPoint(){
syncedRemoveUse(testUserName);
}
}
所以我可以有很多测试类,例如:
public class TestA extends BaseTestCase {
@Factory(dataProvider = "provideTestData")
public TestA(WebDriver webDriver, String testUserName) {
super(webDriver, testUserName);
}
public void someTest() {
WebDriver driver = threadLocalDriver.get();
threadLocalDriver.get().navigate().to("http://myPage.example.com");
logintoMyPageWithUser(driver, testUserName);
doSomeStuffOnPage(driver);
logoutUserFromPage(driver);
}
...
}
所有测试都是通过testNG.xml启动的,如下所示:
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="sample suite" verbose="1" parallel="instances" thread-count="20" data-provider-thread-count="10">
<test name="sample test" >
<packages>
<package name="com.path_to_package_with_example" />
</packages>
</test>
</suite>
这个解决方案的孩子。但是,我讨厌那里的Thread.sleep()。 它创建了许多线程,其中大多数线程一直在等待对方。 我希望将使用同一用户的所有测试排列到同一线程中,并尽量减少等待时间。
答案 0 :(得分:0)
我不知道一种组织测试的方法,其中每个组在一个线程中运行。但是您可以将“用户忙于睡眠时”替换为“尝试锁定用户”。一旦对用户进行了另一项测试(即解除锁定),后者就会继续执行。
下面的可运行示例应该使您开始使用“尝试锁定用户”的想法。请记住,如果您获得了锁(在您的情况下为“ beforeTest”),则必须确保在“ finally”块(在您的情况下为“ afterTest”)中释放该锁。其他执行可能会挂起并且永远不会结束。
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
// https://stackoverflow.com/questions/56474713/parallel-tests-with-resource-lock
public class NamedResourceLocks {
public static void main(String[] args) {
System.out.println("Starting");
ExecutorService executor = Executors.newCachedThreadPool();
try {
new NamedResourceLocks().run(executor);
} catch (Exception e) {
e.printStackTrace();
} finally {
executor.shutdownNow();
}
System.out.println("Done");
}
final static String userPrefix = "user";
final static int maxUsers = 3;
final static long maxWait = 10_000; // 10 seconds
final static long startTime = System.currentTimeMillis();
final Map<String, ReentrantLock> userLocks = new ConcurrentHashMap<>();
final int maxTests = maxUsers * 10;
final CountDownLatch allTestsDone = new CountDownLatch(maxTests);
void run(ExecutorService executor) throws Exception {
IntStream.range(0, maxUsers).forEach(u ->
userLocks.put(userPrefix + u, new ReentrantLock(true)));
IntStream.range(0, maxTests).forEach(t ->
executor.execute(new Test(this, random.nextInt(maxUsers), t)));
if (allTestsDone.await(maxWait, TimeUnit.MILLISECONDS)) {
System.out.println("All tests finished");
}
}
void lock(String user) throws Exception {
ReentrantLock lock = userLocks.get(user);
if (!lock.tryLock(maxWait, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Waited too long.");
}
}
void unlock(String user) {
userLocks.get(user).unlock();
}
void oneTestDone() {
allTestsDone.countDown();
}
final static Random random = new Random();
static class Test implements Runnable {
final NamedResourceLocks locks;
final String user;
final int testNumber;
public Test(NamedResourceLocks locks, int userNumber, int testNumber) {
this.locks = locks;
this.user = userPrefix + userNumber;
this.testNumber = testNumber;
}
@Override
public void run() {
boolean haveLock = false;
try {
log(this, "acquiring lock");
locks.lock(user);
haveLock = true;
int sleepTime = random.nextInt(maxUsers) + 1;
log(this, "sleeping for " + sleepTime + " ms.");
Thread.sleep(sleepTime);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (haveLock) {
log(this, "releasing lock");
locks.unlock(user);
}
locks.oneTestDone();
}
}
}
static void log(Test test, String msg) {
System.out.println((System.currentTimeMillis() - startTime) + " - " +
test.testNumber + " / " + test.user + " - " + msg);
}
}