SBT 集成测试设置

本文介绍了SBT 集成测试设置的处理方法,对大家解决问题具有一定的参考价值

问题描述

我想在我的 SBT + Spray 应用中添加一个集成测试阶段.

I would like to add an Integration Test phase to my SBT + Spray app.

理想情况下,它就像 Maven,具有以下阶段:

Ideally it would be just like Maven, with the following phases:

  • 编译:应用已构建
  • test:运行单元测试
  • pre-integration-test:应用在单独的进程中启动
  • integration-test:运行集成测试;他们向后台运行的应用发出请求并验证返回的结果是否正确
  • post-integration-test:之前启动的应用实例被关闭
  • compile: The app is built
  • test: The unit tests are run
  • pre-integration-test: The app is launched in a separate process
  • integration-test: The integration tests are run; they issue requests to the app running in the background and verify that the correct results are returned
  • post-integration-test: The instance of the app previously launched is shut down

我在让它工作时遇到了很多麻烦.有没有我可以效仿的可行示例?

I'm having a lot of trouble getting this to work. Is there a worked example that I can follow?

1) 单独的it"代码库:

1) Separate "it" codebase:

我首先添加 中显示的代码SBT 文档中的集成测试"部分 复制到 project/Build.scala 处的新文件中.

I started by adding the code shown in the "Integration Test" section of the SBT docs to a new file at project/Build.scala.

这允许我在src/it/scala"下添加一些集成测试并使用sbt it:test"运行它们,但我看不到如何添加 pre-integration-test 钩子.

This allowed me to add some integration tests under "src/it/scala" and to run them with "sbt it:test", but I can't see how to add a pre-integration-test hook.

问题确保重新启动"任务在之前自动运行it:test" 似乎解决了如何设置这样的钩子,但答案对我不起作用(参见 我对那里的评论).

The question "Ensure 're-start' task automatically runs before it:test" seems to address how to set up such a hook, but the answer doesn't work for me (see my comment on there).

此外,将上述代码添加到我的 build.scala 中,sbt re-start"任务完全停止工作:它尝试以it"模式运行应用程序,而不是默认"模式.

Also, adding the above code to my build.scala has stopped the "sbt re-start" task from working at all: it tries to run the app in "it" mode, instead of in "default" mode.

2) 测试"代码库中的集成测试:

2) Integration tests in "test" codebase:

我正在使用 IntelliJ,而单独的it"代码库确实使它感到困惑.它无法编译该目录中的任何代码,因为它认为所有依赖项都丢失了.

I am using IntelliJ, and the separate "it" codebase has really confused it. It can't compile any of the code in that dir, as it thinks that all the dependencies are missing.

我尝试粘贴来自具有共享源的附加测试配置",但出现编译错误:

I tried to paste instead the code from "Additional test configurations with shared sources" from the SBT docs, but I get a compile error:

[error] E:WorkmyprojectprojectBuild.scala:14: not found: value testOptions
[error]         testOptions in Test := Seq(Tests.Filter(unitFilter)),

有没有我可以效仿的可行示例?

Is there a worked example I can follow?

我正在考虑放弃通过 SBT 进行设置,而是添加一个测试标志来将测试标记为集成"并编写一个外部脚本来处理这个问题.

I'm considering giving up on setting this up via SBT and instead adding a test flag to mark tests as "integration" and writing an external script to handle this.

推荐答案

我现在已经编写了自己的代码来执行此操作.我遇到的问题:

I have now written my own code to do this. Issues that I encountered:

  • 我发现将我的 build.sbt 转换为 project/Build.scala 文件修复了大部分编译错误(并且通常会产生很多编译错误更容易修复,因为 IntelliJ 可以更轻松地提供帮助).

  • I found that converting my build.sbt to a project/Build.scala file fixed most of the compile errors (and made compile errors in general much easier to fix, as IntelliJ could help much more easily).

我能找到的在后台进程中启动应用程序的最佳方法是使用 sbt-start-script 并在新进程中调用该脚本.

The nicest way I could find for launching the app in a background process was to use sbt-start-script and to call that script in a new process.

在 Windows 上杀死后台进程非常困难.

Killing the background process was very difficult on Windows.

我的应用程序中的相关代码发布在下面,因为我认为有些人遇到了这个问题.如果有人写了一个 sbt 插件来正确"地做到这一点,我很想听听.

The relevant code from my app is posted below, as I think a few people have had this problem. If anyone writes an sbt plugin to do this "properly", I would love to hear of it.

project/Build.scala的相关代码:

object MyApp extends Build {
  import Dependencies._

  lazy val project = Project("MyApp", file("."))

    // Functional test setup.
    // See http://www.scala-sbt.org/release/docs/Detailed-Topics/Testing#additional-test-configurations-with-shared-sources
    .configs(FunctionalTest)
    .settings(inConfig(FunctionalTest)(Defaults.testTasks) : _*)
    .settings(
      testOptions in Test := Seq(Tests.Filter(unitTestFilter)),
      testOptions in FunctionalTest := Seq(
        Tests.Filter(functionalTestFilter),
        Tests.Setup(FunctionalTestHelper.launchApp _),
        Tests.Cleanup(FunctionalTestHelper.shutdownApp _)),

      // We ask SBT to run 'startScriptForJar' before the functional tests,
      // since the app is run in the background using that script
      test in FunctionalTest <<= (test in FunctionalTest).dependsOn(startScriptForJar in Compile)
    )
    // (other irrelvant ".settings" calls omitted here...)


  lazy val FunctionalTest = config("functional") extend(Test)

  def functionalTestFilter(name: String): Boolean = name endsWith "FuncSpec"
  def unitTestFilter(name: String): Boolean = !functionalTestFilter(name)
}

此帮助代码位于 project/FunctionTestHelper.scala:

import java.net.URL
import scala.concurrent.{TimeoutException, Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.sys.process._

/**
 * Utility methods to help with the FunctionalTest phase of the build
 */
object FunctionalTestHelper {

  /**
   * The local port on which the test app should be hosted.
   */
  val port = "8070"
  val appUrl = new URL("http://localhost:" + port)

  var processAndExitVal: (Process, Future[Int]) = null

  /**
   * Unfortunately a few things here behave differently on Windows
   */
  val isWindows = System.getProperty("os.name").startsWith("Windows")

  /**
   * Starts the app in a background process and waits for it to boot up
   */
  def launchApp(): Unit = {

    if (canConnectTo(appUrl)) {
      throw new IllegalStateException(
        "There is already a service running at " + appUrl)
    }

    val appJavaOpts =
      s"-Dspray.can.server.port=$port " +
      s"-Dmyapp.integrationTests.itMode=true " +
      s"-Dmyapp.externalServiceRootUrl=http://localhost:$port"
    val javaOptsName = if (isWindows) "JOPTS" else "JAVA_OPTS"
    val startFile = if (isWindows) "start.bat" else "start"

    // Launch the app, wait for it to come online
    val process: Process = Process(
      "./target/" + startFile,
      None,
      javaOptsName -> appJavaOpts)
        .run()
    processAndExitVal = (process, Future(process.exitValue()))

    // We add the port on which we launched the app to the System properties
    // for the current process.
    // The functional tests about to run in this process will notice this
    // when they load their config just before they try to connect to the app.
    System.setProperty("myapp.integrationTests.appPort", port)

    // poll until either the app has exited early or we can connect to the
    // app, or timeout
    waitUntilTrue(20.seconds) {
      if (processAndExitVal._2.isCompleted) {
        throw new IllegalStateException("The functional test target app has exited.")
      }
      canConnectTo(appUrl)
    }
  }

  /**
   * Forcibly terminates the process started in 'launchApp'
   */
  def shutdownApp(): Unit = {
    println("Closing the functional test target app")
    if (isWindows)
      shutdownAppOnWindows()
    else
      processAndExitVal._1.destroy()
  }

  /**
   * Java processes on Windows do not respond properly to
   * "destroy()", perhaps because they do not listen to WM_CLOSE messages
   *
   * Also there is no easy way to obtain their PID:
   * http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
   * http://stackoverflow.com/questions/801609/java-processbuilder-process-destroy-not-killing-child-processes-in-winxp
   *
   * http://support.microsoft.com/kb/178893
   * http://stackoverflow.com/questions/14952948/kill-jvm-not-forcibly-from-command-line-in-windows-7
   */
  private def shutdownAppOnWindows(): Unit = {
    // Find the PID of the server process via netstat
    val netstat = "netstat -ano".!!

    val m = s"(?m)^  TCP    127.0.0.1:${port}.* (d+)$$".r.findFirstMatchIn(netstat)

    if (m.isEmpty) {
      println("FunctionalTestHelper: Unable to shut down app -- perhaps it did not start?")
    } else {
      val pid = m.get.group(1).toInt
      s"taskkill /f /pid $pid".!
    }
  }

  /**
   * True if a connection could be made to the given URL
   */
  def canConnectTo(url: URL): Boolean = {
    try {
      url.openConnection()
        .getInputStream()
        .close()
      true
    } catch {
      case _:Exception => false
    }
  }

  /**
   * Polls the given action until it returns true, or throws a TimeoutException
   * if it does not do so within 'timeout'
   */
  def waitUntilTrue(timeout: Duration)(action: => Boolean): Unit = {
    val startTimeMillis = System.currentTimeMillis()
    while (!action) {
      if ((System.currentTimeMillis() - startTimeMillis).millis > timeout) {
        throw new TimeoutException()
      }
    }
  }
}

这篇关于SBT 集成测试设置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2

admin_action_{$_REQUEST[‘action’]}

do_action( "admin_action_{$_REQUEST[‘action’]}" )动作钩子::在发送“Action”请求变量时激发。Action Hook: Fires when an ‘action’ request variable is sent.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$_REQUEST['action']引用从GET或POST请求派生的操作。源码(Source)更新版本源码位置使用被使用2.6.0 wp-admin/admin.php:...

日期:2020-09-02 17:44:16 浏览:1167

admin_footer-{$GLOBALS[‘hook_suffix’]}

do_action( "admin_footer-{$GLOBALS[‘hook_suffix’]}", string $hook_suffix )操作挂钩:在默认页脚脚本之后打印脚本或数据。Action Hook: Print scripts or data after the default footer scripts.目录锚点:#说明#参数#源码说明(Description)钩子名的动态部分,$GLOBALS['hook_suffix']引用当前页的全局钩子后缀。参数(Parameters)参数类...

日期:2020-09-02 17:44:20 浏览:1069

customize_save_{$this->id_data[‘base’]}

do_action( "customize_save_{$this-&gt;id_data[‘base’]}", WP_Customize_Setting $this )动作钩子::在调用WP_Customize_Setting::save()方法时激发。Action Hook: Fires when the WP_Customize_Setting::save() method is called.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_data...

日期:2020-08-15 15:47:24 浏览:806

customize_value_{$this->id_data[‘base’]}

apply_filters( "customize_value_{$this-&gt;id_data[‘base’]}", mixed $default )过滤器::过滤未作为主题模式或选项处理的自定义设置值。Filter Hook: Filter a Customize setting value not handled as a theme_mod or option.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分,$this->id_date['base'],指的是设置...

日期:2020-08-15 15:47:24 浏览:898

get_comment_author_url

过滤钩子:过滤评论作者的URL。Filter Hook: Filters the comment author’s URL.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/comment-template.php:32610...

日期:2020-08-10 23:06:14 浏览:930

network_admin_edit_{$_GET[‘action’]}

do_action( "network_admin_edit_{$_GET[‘action’]}" )操作挂钩:启动请求的处理程序操作。Action Hook: Fires the requested handler action.目录锚点:#说明#源码说明(Description)钩子名称的动态部分$u GET['action']引用请求的操作的名称。源码(Source)更新版本源码位置使用被使用3.1.0 wp-admin/network/edit.php:3600...

日期:2020-08-02 09:56:09 浏览:876

network_sites_updated_message_{$_GET[‘updated’]}

apply_filters( "network_sites_updated_message_{$_GET[‘updated’]}", string $msg )筛选器挂钩:在网络管理中筛选特定的非默认站点更新消息。Filter Hook: Filters a specific, non-default site-updated message in the Network admin.目录锚点:#说明#参数#源码说明(Description)钩子名称的动态部分$_GET['updated']引用了非默认的...

日期:2020-08-02 09:56:03 浏览:863

pre_wp_is_site_initialized

过滤器::过滤在访问数据库之前是否初始化站点的检查。Filter Hook: Filters the check for whether a site is initialized before the database is accessed.目录锚点:#源码源码(Source)更新版本源码位置使用被使用 wp-includes/ms-site.php:93910...

日期:2020-07-29 10:15:38 浏览:832

WordPress 的SEO 教学:如何在网站中加入关键字(Meta Keywords)与Meta 描述(Meta Description)?

你想在WordPress 中添加关键字和meta 描述吗?关键字和meta 描述使你能够提高网站的SEO。在本文中,我们将向你展示如何在WordPress 中正确添加关键字和meta 描述。为什么要在WordPress 中添加关键字和Meta 描述?关键字和说明让搜寻引擎更了解您的帖子和页面的内容。关键词是人们寻找您发布的内容时,可能会搜索的重要词语或片语。而Meta Description则是对你的页面和文章的简要描述。如果你想要了解更多关于中继标签的资讯,可以参考Google的说明。Meta 关键字和描...

日期:2020-10-03 21:18:25 浏览:1716

谷歌的SEO是什么

SEO (Search Engine Optimization)中文是搜寻引擎最佳化,意思近于「关键字自然排序」、「网站排名优化」。简言之,SEO是以搜索引擎(如Google、Bing)为曝光媒体的行销手法。例如搜寻「wordpress教学」,会看到本站的「WordPress教学:12个课程…」排行Google第一:关键字:wordpress教学、wordpress课程…若搜寻「网站架设」,则会看到另一个网页排名第1:关键字:网站架设、架站…以上两个网页,每月从搜寻引擎导入自然流量,达2万4千:每月「有机搜...

日期:2020-10-30 17:23:57 浏览:1307