Параллельные тесты с ресурсом-блокировкой? - PullRequest
0 голосов
/ 06 июня 2019

Фон : теперь используется java JUnit4, желающий перейти на JUnit5 или TestNG.

Текущее состояние : Наличие более 100 тестов Selenium.Большинство из них повторяются через @RunWith (Parameterized.class) в Junit4.(Т.е. создание нескольких экземпляров тестового класса на основе предоставленного набора параметров, как правило, комбинаций типа браузера и идентификатора пользователя.) Совместное использование ограниченного набора примерно из 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 ().Это создает много потоков, и большинство из них продолжают ждать друг друга.Я бы предпочел выстроить все тесты с использованием одного и того же пользователя в одном потоке и минимизировать время ожидания.

1 Ответ

0 голосов
/ 11 июня 2019

Я не знаю, как организовать тесты в группах, где каждая группа работает в одном потоке.Но вы можете заменить «пока пользователь занят сном» на «попытаться заблокировать пользователя».Последний продолжается с выполнением, как только с пользователем будет выполнен другой тест (т. Е. Разблокируется блокировка).

Приведенный ниже пример запуска должен помочь вам начать с идеи "попробовать блокировать пользователя".Помните, что если вы получаете блокировку («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;

// /8950422/parallelnye-testy-s-resursom-blokirovkoi
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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...