在 Gatling 模拟之前启动 spring-boot 应用程序

Launch spring-boot app before Gatling simulation

我有一个由 maven 提供支持的 spring-boot 项目,我想使用 Gatling 运行 加载测试。 此时此刻,我可以 运行 我的模拟输入以下命令:

mvn gatling:execute

它工作正常,但我必须手动启动我的 springboot 应用程序,否则它将无法工作。

这是我的模拟 class :

package main.aperture.gatling

import aperture.config.SpringBootApertureTestingConfiguration
import io.gatling.core.Predef._
import io.gatling.core.structure.ScenarioBuilder
import io.gatling.http.Predef._
import io.gatling.http.protocol.HttpProtocolBuilder
import org.springframework.boot.SpringApplication
import org.springframework.context.ConfigurableApplicationContext

import scala.concurrent.duration._
import scala.language.postfixOps

/**
  * This class is meant to load test main get routes of this app
  * The app has to be launched first (Manually, unfortunately)
  */
class MainSimulation extends Simulation {

    val baseUrl: String = "localhost"
    val port: String = System.getProperty("server.port", "8080")

    val httpConf: HttpProtocolBuilder = http
        .baseURL(s"http://$baseUrl:$port") // Here is the root for all relative URLs

    val scenario_main_get: ScenarioBuilder = scenario("Testing main get routes")
        .exec(http("Request on main route")
            .get("/"))
        .pause(100 milliseconds)
        .exec(http("Request on rooms main route")
            .get("/api/rooms"))
        .pause(100 milliseconds)
        .exec(http("Request on subjects main route")
            .get("/api/subjects"))
        .pause(100 milliseconds)
        .exec(http("Request on supervisors main route")
            .get("/api/supervisors"))
    setUp(scenario_main_get.inject(rampUsers(1000) over (5 seconds)).protocols(httpConf))
}

这是我的pom.xml(我删除了不相关的元素):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-mongodb-data-res</artifactId>
    <packaging>jar</packaging>
    <version>0.1.0</version>

    <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.4.3.RELEASE</version>
    </parent>

    <!-- Properties -->
    <properties>
        <springfox-version>2.6.1</springfox-version>
        <springfox-swaggerui-version>2.6.1</springfox-swaggerui-version>
        <spring-version>4.3.2.RELEASE</spring-version>
        <swagger-core-version>1.5.10</swagger-core-version>
        <slf4j-version>1.6.3</slf4j-version>
        <junit-version>4.8.1</junit-version>
        <hibernate-validator-version>5.2.4.Final</hibernate-validator-version>
        <fizzedwatcher.version>1.0.6</fizzedwatcher.version>
        <gatling.version>2.2.5</gatling.version>
        <gatling-plugin.version>2.2.4</gatling-plugin.version>
        <scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>
    </properties>

    <!-- Project dependencies  -->
    <dependencies>

        <!-- SpringBoot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>


        <!-- Gatling highcharts -->
        <dependency>
            <groupId>io.gatling.highcharts</groupId>
            <artifactId>gatling-charts-highcharts</artifactId>
            <version>${gatling.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <!-- Project build options and plugins -->
    <build>
        <plugins>
            <!--Spring boot plugin -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>

                <configuration>
                    <mainClass>aperture.config.SpringBootApertureTestingConfiguration</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <!-- Scala maven plugin -->
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>${scala-maven-plugin.version}</version>
            </plugin>

            <!-- Gatling maven plugin -->
            <plugin>
                <groupId>io.gatling</groupId>
                <artifactId>gatling-maven-plugin</artifactId>
                <version>${gatling-plugin.version}</version>
                <configuration>
                    <simulationClass>main.aperture.gatling.MainSimulation</simulationClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

我希望能够在 运行 使用 gatling 进行负载测试之前自动 运行 spring-boot 应用程序。 有些人建议在我的模拟开始时添加以下代码:

val app: ConfigurableApplicationContext = SpringApplication.run(classOf[SpringBootApertureTestingConfiguration])

Runtime.getRuntime.addShutdownHook(new Thread() {
    override def run(): Unit = app.stop()
})

但是当我这样做时,我在执行时收到以下错误:

[INFO] --- gatling-maven-plugin:2.2.4:execute (default-cli) @ gs-accessing-mongodb-data-res ---
20:32:23,625 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
20:32:23,625 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
20:32:23,625 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [jar:file:/home/msb/.m2/repository/io/gatling/gatling-maven-plugin/2.2.4/gatling-maven-plugin-2.2.4.jar!/logback.xml]
20:32:23,634 |-INFO in ch.qos.logback.core.joran.spi.ConfigurationWatchList@1a04f701 - URL [jar:file:/home/msb/.m2/repository/io/gatling/gatling-maven-plugin/2.2.4/gatling-maven-plugin-2.2.4.jar!/logback.xml] is not of type file
20:32:23,670 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
20:32:23,674 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
20:32:23,678 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [CONSOLE]
20:32:23,681 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@5:19 - no applicable action for [immediateFlush], current ElementPath  is [[configuration][appender][immediateFlush]]
20:32:23,682 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
20:32:23,721 |-INFO in ch.qos.logback.classic.joran.action.LevelAction - ROOT level set to WARN
20:32:23,721 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [CONSOLE] to Logger[ROOT]
20:32:23,722 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
20:32:23,722 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@4e91d63f - Registering current configuration as safe fallback point

20:32:29,151 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
20:32:29,152 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
20:32:29,152 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [jar:file:/home/msb/.m2/repository/io/gatling/gatling-maven-plugin/2.2.4/gatling-maven-plugin-2.2.4.jar!/logback.xml]
20:32:29,169 |-INFO in ch.qos.logback.core.joran.spi.ConfigurationWatchList@5419f379 - URL [jar:file:/home/msb/.m2/repository/io/gatling/gatling-maven-plugin/2.2.4/gatling-maven-plugin-2.2.4.jar!/logback.xml] is not of type file
20:32:29,221 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
20:32:29,225 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
20:32:29,233 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [CONSOLE]
20:32:29,239 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@5:19 - no applicable action for [immediateFlush], current ElementPath  is [[configuration][appender][immediateFlush]]
20:32:29,241 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
20:32:29,282 |-INFO in ch.qos.logback.classic.joran.action.LevelAction - ROOT level set to WARN
20:32:29,282 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [CONSOLE] to Logger[ROOT]
20:32:29,282 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
20:32:29,283 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@7dc7cbad - Registering current configuration as safe fallback point

main.aperture.gatling.MainSimulation is the only simulation, executing it.
Select simulation id (default is 'mainsimulation'). Accepted characters are a-z, A-Z, 0-9, - and _

Select run description (optional)

20:33:42.287 [restartedMain][WARN ][SpringApplicationRunListeners.java:91] o.s.b.SpringApplication - Error handling failed (no error message)
20:33:42.291 [restartedMain][ERROR][SpringApplication.java:839] o.s.b.SpringApplication - Application startup failed
java.lang.IllegalStateException: Logback configuration error detected: 
ERROR in ch.qos.logback.core.joran.spi.Interpreter@5:19 - no applicable action for [immediateFlush], current ElementPath  is [[configuration][appender][immediateFlush]]
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:161)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.reinitialize(LogbackLoggingSystem.java:205)
        at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:65)
        at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:50)
        at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:114)
        at org.springframework.boot.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:299)
        at org.springframework.boot.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:272)
        at org.springframework.boot.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:235)
        at org.springframework.boot.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:208)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:166)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:121)
        at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:68)
        at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:54)
        at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:337)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1186)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1175)
        at main.aperture.gatling.MainSimulation.<init>(MainSimulation.scala:20)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at java.lang.Class.newInstance(Class.java:442)
        at io.gatling.app.Runner.run0(Runner.scala:79)
        at io.gatling.app.Runner.run(Runner.scala:64)
        at io.gatling.app.Gatling$.start(Gatling.scala:59)
        at io.gatling.app.Gatling$.fromArgs(Gatling.scala:43)
        at io.gatling.app.Gatling$.main(Gatling.scala:35)
        at io.gatling.app.Gatling.main(Gatling.scala)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at io.gatling.mojo.MainWithArgsInFile.runMain(MainWithArgsInFile.java:50)
        at io.gatling.mojo.MainWithArgsInFile.main(MainWithArgsInFile.java:33)
Caused by: org.springframework.boot.devtools.restart.SilentExitExceptionHandler$SilentExitException
        at org.springframework.boot.devtools.restart.SilentExitExceptionHandler.exitCurrentThread(SilentExitExceptionHandler.java:90)
        at org.springframework.boot.devtools.restart.Restarter.immediateRestart(Restarter.java:182)
        at org.springframework.boot.devtools.restart.Restarter.initialize(Restarter.java:161)
        at org.springframework.boot.devtools.restart.Restarter.initialize(Restarter.java:543)
        at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationStartedEvent(RestartApplicationListener.java:68)
        at org.springframework.boot.devtools.restart.RestartApplicationListener.onApplicationEvent(RestartApplicationListener.java:45)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:166)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:138)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:121)
        at org.springframework.boot.context.event.EventPublishingRunListener.started(EventPublishingRunListener.java:63)
        at org.springframework.boot.SpringApplicationRunListeners.started(SpringApplicationRunListeners.java:48)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:304)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1186)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1175)
        at main.aperture.gatling.MainSimulation.<init>(MainSimulation.scala:20)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at java.lang.Class.newInstance(Class.java:442)
        at io.gatling.app.Runner.run0(Runner.scala:79)
        at io.gatling.app.Runner.run(Runner.scala:64)
        at io.gatling.app.Gatling$.start(Gatling.scala:59)
        at io.gatling.app.Gatling$.fromArgs(Gatling.scala:43)
        at io.gatling.app.Gatling$.main(Gatling.scala:35)
        at io.gatling.app.Gatling.main(Gatling.scala)
        ... 6 more

也许我忘记了什么或者我的配置中缺少什么,但我在文档中找不到针对我的特定问题的任何帮助。有人说这是一个 spring-boot 开发工具错误。

有谁知道如何纠正这个问题?

即使我的模拟在我的 spring-boot 项目中的一个包中,也不意味着它链接到该项目。所以,也许想要直接在代码中启动它并不相关。但是,有没有人找到解决方法?

在此先感谢您的帮助。

您是否考虑过将其包装在一个简单的 shell 脚本中并从 spring boot maven 插件启动应用程序?

http://docs.spring.io/spring-boot/docs/1.5.3.RELEASE/maven-plugin/run-mojo.html

否则你应该能够在没有 shell 脚本的情况下将 spring 引导插件挂接到 运行 的 Maven 生命周期中。

Patrick 的回答提供了足够的元素来考虑使用简单的 shell 脚本。如果你遇到过那种问题,这里是我写的代码:

#!/usr/bin/env bash

# ########### FUNCTIONS
trim(){
    [[ "" =~ ^[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# ########### MAIN PROGRAM

# ------ First, launch the spring-boot app in background
mvn spring-boot:run &

# ------ Then, wait for it to be correctly initialized

portExtracted=`awk '/server/,/port/' ./src/main/resources/application.yml | cut -d ":" -f 2`
portToListenTo=`trim ${portExtracted}`

i=0
maxCounter=100
while [[ ! $(lsof -i :"$portToListenTo") ]] && [ ${i} -lt ${maxCounter} ]
do
    echo 'Waiting for spring-boot app to be up and running.'
    sleep 2
    i=$[$i+1]
done

mvn gatling:execute

不过,您必须根据需要调整它:更改配置文件的路径和 awk 命令以动态提取应用程序的端口。

感谢任何增强脚本的建议。

编辑: 我还在 pom 中用以下内容替换了 spring-boot maven 插件描述:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>

    <configuration>
        <mainClass>aperture.config.SpringBootApertureTestingConfiguration</mainClass>
    </configuration>
    <executions>
        <execution>
            <id>pre-integration-test</id>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
        <execution>
            <id>post-integration-test</id>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

这样应用程序会在 gatling 测试 运行 之前启动。为此,只需 运行 mvn verify 命令。如果您不跳过测试,它也适用于 mvn clean install

希望对您有所帮助!

如前所述,简化的脚本可以是:

#!/usr/bin/env bash

set -e -x 

mvn spring-boot:start
mvn gatling:execute
mvn spring-boot:stop

我一直在寻找类似的东西,发现这个有用:

mvn spring-boot:start gatling:test spring-boot:stop

before 挂钩中启动应用程序也可以。 这样做的好处是您可以将应用程序配置(例如 url)设置为外部服务,然后可以对其进行模拟 e.q。使用 WireMock:

在模拟 class 中,添加:

  before {
    val app = SpringApplication.run(classOf[Application])
    app.registerShutdownHook()
  }