Java jvm线程安全吗?

Java jvm线程安全吗?,java,multithreading,gradle,cucumber-jvm,gpars,Java,Multithreading,Gradle,Cucumber Jvm,Gpars,我想在多个线程中运行相同的cumber测试。更具体地说,我有一套功能,在一个线程中运行这些功能就可以了。我使用JSON格式化程序记录每个步骤的运行时间。现在我想做负载测试。我更关心多线程环境中每个功能/步骤的运行时间。所以我创建了多个线程,每个线程运行在同一个功能集上。每个线程都有自己的JSON报告。这在理论上可能吗 由于某些项目设置原因,我无法使用JUnit runner。因此,我不得不求助于CLI方法: long threadId = Thread.currentThread

我想在多个线程中运行相同的cumber测试。更具体地说,我有一套功能,在一个线程中运行这些功能就可以了。我使用JSON格式化程序记录每个步骤的运行时间。现在我想做负载测试。我更关心多线程环境中每个功能/步骤的运行时间。所以我创建了多个线程,每个线程运行在同一个功能集上。每个线程都有自己的JSON报告。这在理论上可能吗

由于某些项目设置原因,我无法使用JUnit runner。因此,我不得不求助于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();      
我试着为每一根黄瓜创建一条单独的线。问题是,只有一个线程具有有效的JSON报告。所有其他线程只创建空JSON文件。这是Cucumber设计的还是我遗漏了什么?

目前还没有——这是你观察到的。我还没有找到任何方法按场景进行并行化


这是一个关于穷人的并发性的好例子。只需运行多个命令,每个命令选择测试的不同子集——按功能或标记。我会分叉一个新的JVM(就像JUnit驱动程序那样),而不是尝试线程,因为cucumber不是为此而设计的。你必须自己平衡它们,然后找出如何组合报告。(但至少问题是合并报表而不是损坏的报表。)

我们已经研究了Gradle和Groovy下的多线程cucumber测试,使用了优秀的。我们有650个用户界面测试和计数

在多线程中运行JVM没有遇到任何明显的问题,但是多线程也没有像我们希望的那样提高性能

我们在单独的线程中运行每个功能文件。有一些细节需要注意,比如将来自不同线程的Cumber报告拼接在一起,并确保我们的step代码是线程安全的。我们有时需要在步骤之间存储值,因此我们使用一个对线程id进行键控的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任务代码的要点,该代码使用GPAR运行多线程测试:

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()
def group=new DefaultPGroup(maxSimultaneousThreads())
def workUnits=features.collect{File featureFile->
组任务{
试一试{
javaexec{
main=“cumber.api.cli.main”
...
args=[
...
“--plugin',“json:$unitReportDir/${featureFile.name}.json”,
...
“--glue”、“src/test/groovy/steps”,
“路径/to/$featureFile”
]
}
}捕获(执行异常){
++努弗罗斯

stackTraces假设您可以通过使用以下Maven POM配置并行运行Cucumber JVM测试:


org.apache.maven.plugins
maven surefire插件
2.14
验收试验
集成测试
测试
${surefire.fork.count}
假的
-Duser.language=en
-Xmx1024m
-XX:MaxPermSize=256m
-文件编码=UTF-8
假的
**/*上课时
真的

在上面的代码片段中,您可以看到maven surefire插件用于运行我们的验收测试–任何以*结尾的类都将作为JUnit测试类运行。多亏了JUnit,使测试并行运行现在是设置forkCount配置选项的一个简单例子。在示例项目中,该选项设置为5,这意味着我们可以n一次最多运行5个线程(即,5个运行程序类)。

如果您可以找到一种方法,让cucumber根据给定的标记为您想要运行的所有场景输出场景位置(即feature_file_path:line_Number_in_feature_file),那么您可以使用GPAR和gradle并行运行场景。 步骤1:在第一个gradle任务中,我们将使用上述解决方案生成一个文本文件(比如scenarios.txt),其中包含我们要执行的所有场景的位置 步骤2:接下来,将步骤1中生成的scenarios.txt的内容提取到groovy列表中,比如scenariosList 步骤3:再创建一个任务(javaExec),这里我们将使用gparswithpool与scenariosList.eachParallel结合使用,并使用cucumber主类和其他cucumberOptions并行运行这些场景。PS:这里我们将提供场景位置作为选项“features”的值所以cucumber将只运行这个场景。也不需要提供任何标记名,因为我们已经有了需要执行的场景列表

注意:您需要使用具有高配置的机器,如Linux服务器,因为每个场景都会创建一个新的jvm实例,并且可能使用Saucelabs之类的云服务来执行场景。这样,您就不必担心基础设施

步骤4:这是最后一步。步骤3中的每个场景都将生成一个json输出文件。您必须根据功能名称整理输出,以便为每个功能文件生成一个json文件

这个解决方案听起来有点复杂,但只要付出正确的努力,就可以产生显著的结果。

我创建了这样一个解决方案:并行运行功能文件。。但是拉请求没有得到批准,因为这是基于JUnitRunner的
<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>