Является ли Cucumber-jvm потокобезопасным?

Я хочу запускать одни и те же тесты Cucumber в нескольких потоках. В частности, у меня есть набор функций, и запуск этих функций в одном потоке работает нормально. Я использую средство форматирования JSON для записи времени выполнения каждого шага. Теперь я хочу сделать нагрузочный тест. Меня больше волнует время работы каждой функции/шага в многопоточной среде. Поэтому я создаю несколько потоков, и каждый поток работает с одним и тем же набором функций. Каждый поток имеет свой собственный отчет JSON. Возможно ли это в теории?

По какой-то причине настройки проекта я не могу использовать бегун JUnit. Поэтому я должен прибегнуть к CLI-способу:

        long threadId = Thread.currentThread().getId();
        String jsonFilename = String.format("json:run/cucumber%d.json", threadId);

            String argv[] = new String[]{
                "--glue",
                "com.some.package",
                "--format",
                jsonFilename,
                "d:\\features"};

        // Do not call Main.run() directly. It has a System.exit() call at the end.             
        // Main.run(argv, Thread.currentThread().getContextClassLoader());

        // Copied the same code from Main.run(). 
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        RuntimeOptions runtimeOptions = new RuntimeOptions(new Env("cucumber-jvm"), argv);
        ResourceLoader resourceLoader = new MultiLoader(classLoader);
        ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
        Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
        runtime.writeStepdefsJson();
        runtime.run();      

Я попытался создать отдельный поток для каждого запуска Cucumber. Проблема в том, что только один из потоков имеет действительный отчет JSON. Все остальные потоки просто создают пустые файлы JSON. Это задумано в Cucumber или я что-то пропустил?


person fhcat    schedule 28.07.2014    source источник
comment
Я создал это: github.com/mrunalgosar/cucumber-jvm для параллельного запуска файлов функций. .Но запрос на вытягивание не был одобрен, так как он основан на JUnitRunners.   -  person Mrunal Gosar    schedule 13.04.2016


Ответы (4)


Мы изучили многопоточные тесты на огурцы в Gradle и Groovy, используя превосходную библиотеку GPars. У нас есть 650 тестов пользовательского интерфейса, и их число продолжает расти.

Мы не столкнулись с какими-либо явными проблемами при запуске Cucumber-JVM в нескольких потоках, но многопоточность также не улучшила производительность так сильно, как мы надеялись.

Мы запускали каждый файл функций в отдельном потоке. Есть несколько деталей, о которых нужно позаботиться, например, объединить отчеты об огурцах из разных потоков и убедиться, что наш код шага является потокобезопасным. Иногда нам нужно хранить значения между шагами, поэтому мы использовали concurrentHashMap с ключом к идентификатору потока для хранения таких данных:

class ThreadedStorage {
    static private ConcurrentHashMap multiThreadedStorage = [:]

    static private String threadSafeKey(unThreadSafeKey) {
        def threadId = Thread.currentThread().toString()
        "$threadId:$unThreadSafeKey"
    }

    static private void threadSafeStore(key, value) {
        multiThreadedStorage[threadSafeKey(key)] = value
    }

    def static private threadSafeRetrieve(key) {
        multiThreadedStorage[threadSafeKey(key)]
    }


}

А вот суть кода задачи Gradle, которая запускает многопоточные тесты с использованием GPars:

def group = new DefaultPGroup(maxSimultaneousThreads())
def workUnits = features.collect { File featureFile ->
    group.task {
        try {
            javaexec {
                main = "cucumber.api.cli.Main"
                ...
                args = [
                     ...
                     '--plugin', "json:$unitReportDir/${featureFile.name}.json",
                             ...
                             '--glue', 'src/test/groovy/steps',
                             "path/to/$featureFile"
                    ]
            }
        } catch (ExecException e) {
                ++noOfErrors
                stackTraces << [featureFile, e.getStackTrace()]
        }
    }
}
// ensure all tests have run before reporting and finishing gradle task
workUnits*.join()

Мы обнаружили, что для достижения наилучших результатов необходимо представить файлы функций в порядке, обратном времени выполнения.

Результаты были на 30% лучше, чем у процессора i5, с ухудшением производительности более 4 одновременных потоков, что немного разочаровывало.

Я думаю, что потоки были слишком тяжелыми для многопоточности на нашем оборудовании. Выше определенного количества потоков было слишком много промахов кеша ЦП.

Параллельный запуск на разных экземплярах с использованием потокобезопасной рабочей очереди, такой как Amazon SQS, теперь кажется хорошим шагом вперед, особенно потому, что он не будет страдать от проблем с потокобезопасностью (по крайней мере, не на стороне тестовой среды).

Для нас нетривиально протестировать этот метод многопоточности на оборудовании i7 из-за ограничений безопасности на нашем рабочем месте, но мне было бы очень интересно узнать, как сравнится i7 с большим кешем ЦП и большим количеством физических ядер.

person Nick    schedule 13.04.2016

В настоящее время нет — вот проблема, которую вы заметили. Я не нашел способа распараллелить по сценарию.

Вот хорошая заметка о параллелизме бедняка. Просто запустите несколько команд, каждая из которых выбирает разные подмножества ваших тестов — по функциям или тегам. Я бы разветвил новую JVM (как это сделал бы драйвер JUnit), а не пытался нарезать ее, поскольку огурец не был предназначен для этого. Вы должны сбалансировать их самостоятельно, а затем выяснить, как объединить отчеты. (Но, по крайней мере, проблема заключается в объединении отчетов, а не в поврежденных отчетах.)

person Peter Davis    schedule 15.04.2015

Предположительно, вы можете запускать тесты Cucumber-JVM параллельно, используя эту конфигурацию Maven POM отсюда: https://opencredo.com/running-cucumber-jvm-tests-in-parallel/

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.14</version>
    <executions>
        <execution>
            <id>acceptance-test</id>
            <phase>integration-test</phase>
            <goals>
                <goal>test</goal>
            </goals>
            <configuration>
                <forkCount>${surefire.fork.count}</forkCount>
                <refuseForks>false</reuseForks>
                <argLine>-Duser.language=en</argLine>
                <argLine>-Xmx1024m</argLine>
                <argLine>-XX:MaxPermSize=256m</argLine>
                <argLine>-Dfile.encoding=UTF-8</argLine>
                <useFile>false</useFile>
                <includes>
                    <include>**/*AT.class</include>
                </includes>
                <testFailureIgnore>true</testFailureIgnore>
            </configuration>
        </execution>
    </executions>
</plugin>

В приведенном выше фрагменте вы можете видеть, что плагин maven-surefire-plugin используется для запуска наших приемочных тестов — любые классы, оканчивающиеся на *AT, будут запускаться как тестовый класс JUnit. Благодаря JUnit параллельный запуск тестов теперь представляет собой простой случай установки параметра конфигурации forkCount. В примере проекта установлено значение 5, что означает, что мы можем запускать до 5 потоков (т. е. 5 классов исполнителей) одновременно.

person Katie    schedule 09.11.2016

Что ж, если вы можете найти способ, с помощью которого огурец выводит местоположение сценария (например, feature_file_path:line_nunber_in_feature_file) для всех сценариев, которые вы хотите запустить на основе заданного тега, тогда вы можете использовать gpars и gradle для параллельного запуска сценариев. Шаг 1: В первой задаче Gradle мы будем использовать вышеуказанное решение для создания текстового файла (скажем, сценария.txt), содержащего местоположения для всех сценариев, которые мы хотим выполнить. Шаг 2: Затем извлеките содержимое сценария.txt, сгенерированного в шаг 1 в заводной список, скажем, scriptsList Шаг 3: создайте еще одну задачу (javaExec), здесь мы будем использовать gpars withPool в сочетании со сценариямиList.eachParallel, а также использовать главный класс огурца и другие огуречные параметры для параллельного запуска этих сценариев. PS: здесь мы укажем местоположение сценария в качестве значения параметра «features», чтобы огурец запускал только этот сценарий. Также нет необходимости указывать какое-либо имя тега, поскольку у нас уже есть список сценариев, которые нам нужно выполнить.

Примечание. Вам необходимо использовать машину с высокой конфигурацией, такую ​​как сервер Linux, потому что для каждого сценария создается новый экземпляр jvm, и, возможно, для выполнения сценариев используется облачная служба, такая как Saucelabs. Таким образом, вам не нужно беспокоиться об инфраструктуре.

Шаг 4: Это последний шаг. Каждый сценарий, созданный на шаге 3, будет генерировать выходной файл json. Вы должны сопоставить выходные данные на основе имен функций, чтобы создать один файл json для каждого файла функций.

Это решение звучит немного сложно, но при правильных усилиях оно может дать значительные результаты.

person Ishan007    schedule 13.04.2019