使用Gradle运行Spring Boot REST服务的集成测试

3
我目前正在尝试为一个构建在以下技术上的 REST 服务设置集成测试框架:
  1. Spring-boot
  2. Gradle
  3. Jetty
我能够使用spring-boot 集成测试框架spring-boot junit运行器带起应用上下文,成功地运行测试。
接下来,我想做的事情是有一个gradle任务,它将执行以下操作:
  1. 构建jar文件(非war)
  2. 启动jetty并部署jar文件
  3. 针对这个jar运行一系列测试用例。
  4. 停止jetty
=> 我尝试使用“jetty”插件。但它似乎不支持jar文件。
=> 然后我尝试使用JavaExec任务来运行jar文件,然后运行测试,但我无法找到一种简单直接的方法,在测试完成后停止jar进程。
=> 使用Exec类型任务也存在同样的问题。
因此,我有两个关于此的问题:
  1. 是否有一种方法可以使用gradle实现上述形式的集成测试。

  2. 这种集成测试的方式是否推荐,还是有更好的方法来实现?

非常感谢您提供任何想法和见解。
谢谢,
1个回答

3

有不同的方法可以实现你想要的。我在帮助客户时使用的方法是依赖于Spring Boot Actuator提供的/shutdown URL。

重要提示:如果您使用此方法,请务必在生产环境下禁用或保护/shutdown端点

在构建文件中,你有两个任务:

task startWebApp(type: StartApp) {
    dependsOn 'assemble'
    jarFile = jar.archivePath
    port = 8080
    appContext = "MyApp"
}

task stopWebApp(type: StopApp) {
    urlPath = "${startWebApp.baseUrl}/shutdown"
}

您需要确保您的集成测试依赖于startWebApp任务,并且它们应该由stop任务完成。类似这样的:

integTest.dependsOn "startWebApp"
integTest.finalizedBy "stopWebApp"

当然,您还需要创建自定义任务实现:
class StartApp extends DefaultTask {
    static enum Status { UP, DOWN, TIMED_OUT }

    @InputFile
    File jarFile

    @Input
    int port = 8080

    @Input
    String appContext = ""

    String getBaseUrl() {
        return "http://localhost:${port}" + (appContext ? '/' + appContext : '')
    }

    @TaskAction
    def startApp() {
        logger.info "Starting server"
        logger.debug "Application jar file: " + jarFile

        def args = ["java",
                "-Dspring.profiles.active=dev",
                "-jar",
                jarFile.path]
        def pb = new ProcessBuilder(args)
        pb.redirectErrorStream(true)

        final process = pb.start()
        final output = new StringBuffer()
        process.consumeProcessOutputStream(output)

        def status = Status.TIMED_OUT
        for (i in 0..20) {
            Thread.sleep(3000)

            if (hasServerExited(process)) {
                status = Status.DOWN
                break
            }

            try {
                status = checkServerStatus()
                break
            }
            catch (ex) {
                logger.debug "Error accessing app health URL: " + ex.message
            }
        }

        if (status == Status.TIMED_OUT) process.destroy()

        if (status != Status.UP) {
            logger.info "Server output"
            logger.info "-------------"
            logger.info output.toString()
            throw new RuntimeException("Server failed to start up. Status: ${status}")
        }
    }

    protected Status checkServerStatus() {
        URL url = new URL("$baseUrl/health")
        logger.info("Health Check --> ${url}")
        HttpURLConnection connection = url.openConnection()
        connection.readTimeout = 300

        def obj = new JsonSlurper().parse(
            connection.inputStream,
            connection.contentEncoding ?: "UTF-8")
        connection.inputStream.close()
        return obj.status == "UP" ? Status.UP : Status.DOWN
    }

    protected boolean hasServerExited(Process process) {
        try {
            process.exitValue()
            return true
        } catch (IllegalThreadStateException ex) {
            return false
        }
    }
}

请注意,将服务器放在一个线程上运行非常重要,否则任务永远不会结束。停止服务器的任务更加直接:
class StopApp extends DefaultTask {

    @Input
    String urlPath

    @TaskAction
    def stopApp(){
        def url = new URL(urlPath)
        def connection = url.openConnection()
        connection.requestMethod = "POST"
        connection.doOutput = true
        connection.outputStream.close()
        connection.inputStream.close()
    }
}

它基本上会向 /shutdown URL 发送一个空的 POST 请求来停止正在运行的服务器。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接