在scala规范中同步方法之前/之后?

时间:2014-06-21 05:50:19

标签: scala elasticsearch

class TokenSearchTest extends Specification {
  "Dummy test should" should {
    "work" in new TokensSearch {
      // Do things that assume that the
      // before method has done its work.

      true must beTrue
    }
  }
}

trait TokensSearch extends BeforeAfter {
  lazy val elasticSearchTestHost = "http://localhost:9200/"
  lazy val elasticSearchTestIndex = elasticSearchTestHost + "test_index"
  def before = {
    println("Setting up tokens index.")
    val resultFuture = WS.url(elasticSearchTestIndex).put("")
    Await.result(resultFuture, 5 seconds)
    resultFuture.map{ response => println("Put: " + response.json) }
  }
  def after = {
    println("Deleting tokens index.")
    val resultFuture = WS.url(elasticSearchTestIndex).delete()
    Await.result(resultFuture, 5 seconds)
    resultFuture.map{ response => println("Delete: " + response.json) }
  }
}

如果我不包含Await.result,之前和之后的方法有时会交错,导致放置和删除没有以正确的顺序发生(删除正在尝试 >放置发生):

Delete: {"error":"IndexMissingException[[test_index] missing]","status":404}
Put: {"acknowledged":true}

我是scala的新手,所以不知道这是否是实现此并发性的最佳方式,或者我是否误解了此测试的运行方式。

在这里使用Await好吗?有更常见的做法吗?还有其他意见吗?

2 个答案:

答案 0 :(得分:4)

对我来说使用Await似乎没关系,因为如果你没有同步进行before调用,你确实无法保证它会在内部测试开始之前完成(毕竟,能够在某种方法完成之前继续流动是使用期货的全部意义,对吧?:))

如果您需要确保某些事情的顺序,那么Await是执行此AFAIK的标准方法之一。也许您可以添加recoverWith来更优雅地处理超时。

答案 1 :(得分:1)

注意:代码目前尚未经过测试,可能无法编译。我将在稍后检查它,当我在一台安装了Scala的机器上时。

更新:现在测试代码

不要在测试代码中使用裸等待同步

您编写的代码很好,可以正常工作。使用等待进行同步可以解决您的问题。

但这种方法不会扩展。当您使用不同的灯具添加越来越多的测试时。在并发情况下,通常一些测试会不时失败。

一点理论

在一个更大的项目中,您只想看到真正的错误。在测试单元中根深蒂固的问题将绑定必须分析它们的开发人员并隐藏真正的问题。

不同的测试结果

当您与多个开发人员长期运行一个更大的项目时,您应该能够在测试失败时区分以下原因:

  • 错误的测试结果(证明存在错误)
  • 测试设置中的问题,例如夹具不正确,环境变慢等等(你不知道是否有错误)

在并发情况下,您会收到其他错误来源:

  • 您的测试结果错误(证明存在错误)
  • 你根本没有得到结果,例如因为超时(你不知道是否有错误)

如何处理测试问题

由于环境问题和超时问题,有一个简单的策略:多次重启测试(可能在重启后甚至在另一台机器上)并检查错误是否消失。

告诉测试工具

现代测试工具可以在某种程度上帮助您解决上述问题。但是你必须告诉测试框架你要做什么。有了这些附加信息,现代测试和集成框架,并做了很多令人惊奇的事情,例如:

  • 由于临时问题(环境,并发)而可能导致失败的重新运行测试
  • 准确地显示,哪个更改引入了一个真正的错误
  • 首先运行测试,这可能会在最近更改的代码中找到问题

要使其工作,测试和集成框架必须知道,如果夹具失败或测试,是否有错误的结果或根本没有结果。

修复您的情况

设置失败

有一个简单的解决方法:当灯具出现问题时,检查灯具并在设置方法中提前失效。然后,您的测试框架将看到问题出现在夹具中并跳过相关测试。

为此,单独等待是不够的。如果出现问题,您还必须在等待后检查结果并失败。

链接并发

在我看过的大多数代码中,大多数测试方法都有自己的夹具。虽然一个大型夹具易于安装,但维护起来非常困难。交织在一起的测试数据真的难以重构和改变。从长远来看,为每个测试提供自己的测试数据通常会更便宜。

执行此操作时,您可以使用期货进行测试来链接期货以创建和清理夹具。您可以使用前置条件(例如assume)检查灯具,并使用测试框架中的方法等待测试结果。这样可以在测试失败时明确区分不同的原因。

示例代码

被测单位

我为key-value-store创建了以下虚拟实现:

import io.netty.handler.codec.http.HttpHeaders
import io.netty.util.CharsetUtil
import netcaty.http.server.Server
import scala.util.Random

object HttpServer {
  var server = Option.empty[Server]
  val random = new Random()

  val port: Int = 9200

  def start() {
    stop()
    server=Option(netcaty.Http.respond(port, { (req, res) =>
      if (random.nextInt(8)<=0) Thread.sleep(5000)
      val responseText=if (random.nextInt(8)<=0) """{error: "Something went wrong"}""" else """{"acknowledged":true}"""
      val responseBytes=responseText.getBytes(CharsetUtil.UTF_8)
      res.content().writeBytes(responseBytes)
      res.headers.set(HttpHeaders.Names.CONTENT_LENGTH, responseBytes.length)
    }))
  }

  def stop() {
    server.map(_.stop())
    server=None
  }
}

这个假人导致夹具设置失败并且有一定概率的超时问题。

测试类

测试类使用夹具和测试代码的链接。它使用测试框架中的方法等待测试结果。该框架可以检测(并重新运行)发生超时的测试。夹具代码中的问题在前提条件中被识别,因此可以与被测单元中的错误区分开来:

import org.scalatest.concurrent.AsyncAssertions.Waiter
import org.scalatest.{Matchers, FlatSpec, BeforeAndAfter}
import org.scalatest.concurrent.{ScalaFutures, Futures, PatienceConfiguration}
import play.api.libs.ws.WS
import scala.concurrent.ExecutionContext.Implicits.global
import play.api.test._

class HttpServerTest extends FlatSpec with Matchers with BeforeAndAfter with ScalaFutures {

  val elasticSearchTestHost = "http://localhost:9200/"
  val elasticSearchTestIndex = elasticSearchTestHost + "test_index"

  implicit val application = FakeApplication()

  before {
    HttpServer.start()
  }

  after {
    HttpServer.stop()
  }


  def initTokens() = WS.url(elasticSearchTestIndex).put("")
  def cleanTokens() = WS.url(elasticSearchTestIndex).delete()

  "Async assertions" should "work in futures" in {
    val w = new Waiter

    initTokens.map{response =>
      w {assume(response.body === """{"acknowledged":true}""")} // Check fixture
      w { "some test" should not be empty}
    }.flatMap(res => cleanTokens().map(Function.const(res)))

    w.await()
  }

}

您也可以检查最终结果,而不是服务员:

  "Waiting for a future" should "work in concurrent situations" in {
    val f=initTokens.map(Function.const("do some tests here")).flatMap(res => cleanTokens().map(Function.const(res)))

    f.futureValue should equal("""do some tests here""")

    whenReady(f){response : String =>
      response should equal("""do some tests here""")
    }
  }

注意:检查此处缺少前提条件

如果超时,您将获得以下测试结果:

  

等待未来完成的超时。查询11次,   每次查询之间睡15毫秒。   org.scalatest.concurrent.Futures $ FutureConcept $$ anon $ 1:超时   发生在等待未来完成。查询11次,睡觉   每次查询之间15毫秒。

在我看来,这是一个清晰简洁的错误信息。它可以自动处理,框架可以将超时问题与被测单元中的错误区分开来。

SBT文件

scalaVersion := "2.11.1"

scalacOptions ++= List("-feature","-deprecation", "-unchecked", "-Xlint")

resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "2.1.6" % "test",
  "com.typesafe.play" %% "play-ws" % "2.3.1",
  "com.typesafe.play" %% "play-integration-test" % "2.3.1",
  "tv.cntt" %% "netcaty" % "1.3"
)

是的斯卡拉!

作为旁注:我对Scala丰富的生态系统印象深刻。以上是并发测试的完整,可运行的示例,以及简单的HTTP服务器。