在所有 Scalatest 测试之前或之后执行某些操作

我有一套测试 RESTful API 不同端点的 Scalatest 测试。 我真的希望他们分成不同的文件,最好的组织。

我的问题是如何在所有测试之前启动一些东西(在我的例子中是一个 HTTP 服务器,但它是什么并不重要) ,并在所有测试完成之后关闭它。

我知道 Before AndAfterAll,但是它只能在一个测试文件中完成 before/after。我需要这样的东西,但所有的测试,例如:

——在测试前启动 http 服务器
运行所有的测试套件
——关闭 http 服务器

37814 次浏览

Ok, found a way. It seems (unless someone here can correct me) that Scalatest does not have the facility of a "master" suite. But... you can kinda build one.

You can compose a suite from traits. So using my endpoint example:

class EndpointTests extends FunSpec with MustMatchers with BeforeAndAfterAll
with Foo with Bar {
override def beforeAll(configMap: Map[String, Any]) {
println("Before!")  // start up your web server or whatever
}


override def afterAll(configMap: Map[String, Any]) {
println("After!")  // shut down the web server
}
}

Ok, but what about the tests? Notice the with Foo with Bar. I'm bringing the dependent tests in as traits. See here:

trait Foo extends FunSpec with MustMatchers {
describe("Message here...") {
it("Must do something") {  }
it("Must be ok") {  }
}
}


trait Bar extends FunSpec with MustMatchers {
describe("Hello you...") {
it("One more!") {  }
}
}

The intended way to do this is to use nested suites. Suite has a nestedSuites method that returns an IndexedSeq[Suite] (in 2.0, in 1.9.1 it was a List[Suite]). Suite also has a runNestedSuites method that is responsible for executing any nested suites. By default runNestedSuites calls nestedSuites, and on each returned Suite either invokes run directly, or if a Distributor is passed, puts the nested suites in the distributor so that they can be run in parallel.

So what you really probably want to do is make Foo and Bar into classes, and return instances of them from the nestedSuites method of EndpointTests. There's a class that makes that easy called Suites. Here's an example of its use:

import org.scalatest._
import matchers.MustMatchers


class Foo extends FunSpec with MustMatchers {
describe("Message here...") {
it("Must do something") {  }
it("Must be ok") {  }
}
}


class Bar extends FunSpec with MustMatchers {
describe("Hello you...") {
it("One more!") {  }
}
}


class EndpointTests extends Suites(new Foo, new Bar) with BeforeAndAfterAll {


override def beforeAll(configMap: Map[String, Any]) {
println("Before!")  // start up your web server or whatever
}


override def afterAll(configMap: Map[String, Any]) {
println("After!")  // shut down the web server
}
}

One potential problem, though, is that if you are using discovery to find Suites to run, all three of EndpointTests, Foo, and Bar will be discovered. In ScalaTest 2.0 you can annotate Foo and Bar with @DoNotDiscover, and ScalaTest's Runner will not discover them. But sbt still will. We are currently enhancing sbt so that it passes over otherwise discoverable Suites that are annotated with DoNotDiscover, but this will be in sbt 0.13, which isn't out yet. In the meantime you can get sbt to ignore them by adding an unused constructor parameter to Foo and Bar.

Alternatively you can just use an object.

object TestServer {
startServer()
}

When you access the object it will be initialised, starting the server. Just create a common trait in the body of which you access the object. Then mixin that trait into all your tests. Done.

If your server runs in daemon mode (e.g. a Play! application in test mode) it will be automatically shut down after all tests are run.