运行测试

测试可以从 sbt 运行,也可以从 IDE 运行。从 IDE 运行测试将特定于 IDE,因此我们将在这里集中讨论如何从 sbt 运行测试。

  • 要运行所有测试,请运行test
  • 要只运行一个测试类,请运行testOnly,后跟类的名称,即testOnly com.example.MyTest。支持通配符,例如可以运行testOnly *.MyTest
  • 若要只运行覆盖已更改代码的测试,请运行testQuick
  • 要持续运行测试,请在运行命令前面加波浪线,即~testQuick

测试库

您可以使用Lagom的任何测试框架,流行的包括 ScalaTestSpecs2 。如果您不确定使用哪个,或者没有偏好,我们使用ScalaTest来测试Lagom本身,就像这边文档里写的那样。
除了测试框架之外,Lagom还提供了一个用于测试通用Lagom组件的助手库,称为Lagom testkit。
要使用您首选的测试框架和Lagom testkit,您需要将它们添加到您的库依赖项中,如下所示:

  1. libraryDependencies ++= Seq(
  2. lagomScaladslTestKit,
  3. "org.scalatest" %% "scalatest" % "3.0.1" % Test
  4. )

您可能想在多个地方使用scalaTest,通常可以创建一个单一的val变量定义,这意味着你可以在需要的地方引用val,而不必每次都重新 group id, artifact id 和版本。可以这样做:

  1. val scalaTest = "org.scalatest" %% "scalatest" % "3.0.1" % "test"

然后你可以在libraryDependencies中使用它,只需简单地引用它:

  1. libraryDependencies ++= Seq(
  2. lagomScaladslTestKit,
  3. scalaTest
  4. )

当使用Cassandra时,测试必须是分叉的,这可以通过在项目的构建中添加以下内容来启用:

  1. lazy val `hello-impl` = (project in file("hello-impl"))
  2. .enablePlugins(LagomScala)
  3. .settings(lagomForkedTestSettings: _*)
  4. .settings(
  5. // ...
  6. )

如何测试一个服务

Lagom支持单独为一个服务编写功能测试。服务在服务器中运行,在测试中,你可以使用它的服务客户端与它交互,也就是调用服务API。这些实用程序在ServiceTest中定义。
简单的示例如下:

  1. import com.lightbend.lagom.scaladsl.server.LocalServiceLocator
  2. import com.lightbend.lagom.scaladsl.testkit.ServiceTest
  3. import org.scalatest.AsyncWordSpec
  4. import org.scalatest.Matchers
  5. class HelloServiceSpec extends AsyncWordSpec with Matchers {
  6. "The HelloService" should {
  7. "say hello" in ServiceTest.withServer(ServiceTest.defaultSetup) { ctx =>
  8. new HelloApplication(ctx) with LocalServiceLocator
  9. } { server =>
  10. val client = server.serviceClient.implement[HelloService]
  11. client.sayHello.invoke("Alice").map { response =>
  12. response should ===("Hello Alice!")
  13. }
  14. }
  15. }
  16. }

这里有几点需要注意:

  • 该测试使用了ScalaTest的异步测试支持。实际的测试本身返回一个Future, ScalaTest确保这个future得到适当的处理。
  • withServer接受三个参数。第一个是设置参数,它可以用来配置应该如何设置环境,例如,它可以用来启动Cassandra。第二个是LagomApplication的构造函数,我们在这里构造应用程序,第三个是要运行的块,它接受已启动的服务并运行实际的测试。
  • 当我们构造LagomApplication时,我们混合了LocalServiceLocator。这提供了一个本地服务定位器,它将解析应用程序本身正在运行的服务,这也是构造的服务客户端如何知道在哪里可以找到正在运行服务的依据。
  • 在测试回调中,我们实现了一个服务客户端,然后可以使用它与我们的服务通信。

上面的规范将为每个测试启动一个服务器,这通常很方便,因为它保证了每个测试之间的干净状态。然而,有时为每个测试启动一个服务器可能非常昂贵,特别是当涉及到数据库时。在这些情况下,最好在一个测试的所有测试用例之间共享服务器。要做到这一点,可以使用 startServer,并在整个测试结束之后调用 stop:

  1. import com.lightbend.lagom.scaladsl.server.LocalServiceLocator
  2. import com.lightbend.lagom.scaladsl.testkit.ServiceTest
  3. import org.scalatest.AsyncWordSpec
  4. import org.scalatest.Matchers
  5. import org.scalatest.BeforeAndAfterAll
  6. class HelloServiceSpec extends AsyncWordSpec with Matchers with BeforeAndAfterAll {
  7. lazy val server = ServiceTest.startServer(ServiceTest.defaultSetup) { ctx =>
  8. new HelloApplication(ctx) with LocalServiceLocator
  9. }
  10. lazy val client = server.serviceClient.implement[HelloService]
  11. "The HelloService" should {
  12. "say hello" in {
  13. client.sayHello.invoke("Alice").map { response =>
  14. response should ===("Hello Alice!")
  15. }
  16. }
  17. }
  18. protected override def beforeAll() = server
  19. protected override def afterAll() = server.stop()
  20. }

必须通过在 LagomApplication构造函数的回调中重写对其他服务的依赖,以存根或模拟实现替换它们。如果我们正在为 HelloService编写一个测试,并且它对 GreetingService有一个依赖关系,那么我们必须创建 GreetingService的一个存根实现,它可以用于测试,而不需要运行真正的问候服务。它可能看起来像这样:

  1. lazy val server = ServiceTest.startServer(ServiceTest.defaultSetup) { ctx =>
  2. new HelloApplication(ctx) with LocalServiceLocator {
  3. override lazy val greetingService = new GreetingService {
  4. override def greeting = ServiceCall { _ =>
  5. Future.successful("Hello")
  6. }
  7. }
  8. }
  9. }

默认情况下,服务器运行时禁用 pubsub、 cluster 和持久性特性。您可能希望在Setup中启用集群:

  1. lazy val server = ServiceTest.startServer(
  2. ServiceTest.defaultSetup.withCluster()
  3. ) { ctx =>
  4. new HelloApplication(ctx) with LocalServiceLocator
  5. }

如果您的服务需要持久性,那么您将需要显式地启用它。这可以通过启用Cassandra或JDBC来实现,这取决于您的服务使用了哪种持久性。在任何情况下,Lagom持久性都需要集群,因此当启用一个或另一个时,集群也将自动启用。
您不能同时启用(Cassandra和JDBC)进行测试,如果您在写和读端混合持久性,这可能是一个问题。如果您使用Cassandra 作为写端,JDBC用于读端,只需启用Cassandra 。
启用 Cassandra 持久化:

  1. lazy val server = ServiceTest.startServer(
  2. ServiceTest.defaultSetup.withCassandra()
  3. ) { ctx =>
  4. new HelloApplication(ctx) with LocalServiceLocator
  5. }

启用 JDBC 持久化:

  1. lazy val server = ServiceTest.startServer(
  2. ServiceTest.defaultSetup.withJdbc()
  3. ) { ctx =>
  4. new HelloApplication(ctx) with LocalServiceLocator
  5. }

没有办法显式地启用或禁用pubsub。当集群被启用时(显式地通过启用Cassandra或JDBC),pubsub将可用。

如何在测试中使用TLS

要在测试中使用TestServer服务上的SSL端口,您可以使用withSsl启用SSL支持:

  1. Setup.defaultSetup.withSsl()

启用SSL将自动打开一个新的随机端口,并提供一个javax.net.ssl.SSLContext。测试服务器上的SSLContext。Lagom目前不提供任何允许向HTTPS端口发送请求的客户端工厂。您应该使用Play WS、Akka HTTP或Akka gRPC创建HTTP客户端。然后,使用testServer实例提供的httpsPortsslContext发送请求。请注意,提供的SSLContext是由Lagom的testkit构建的,用于信任testServer证书。最后,由于服务器证书是为CN=localhost颁发的,因此您必须确保您生成请求的authority,否则服务器可能会拒绝并使请求失败。目前,无法使用不同的SSL证书设置测试服务器。

  1. "complete a WS call over HTTPS" in {
  2. val setup = defaultSetup.withSsl()
  3. ServiceTest.withServer(setup)(new TestTlsApplication(_)) { server =>
  4. implicit val actorSystem = server.application.actorSystem
  5. implicit val ctx = server.application.executionContext
  6. // To explicitly use HTTPS on a test you must create a client of your
  7. // own and make sure it uses the provided SSLContext
  8. val wsClient = buildCustomWS(server.clientSslContext.get)
  9. // use `localhost` as authority
  10. val url = s"https://localhost:${server.playServer.httpsPort.get}/api/sample"
  11. val response =
  12. wsClient
  13. .url(url)
  14. .get()
  15. .map {
  16. _.body[String]
  17. }
  18. whenReady(response, timeout) { r =>
  19. r should be("sample response")
  20. }
  21. }
  22. }

如何测试多个服务

Lagom将为编写涉及多个交互服务的集成测试提供支持。测试多个服务这个功能尚未实现。

如何测试流式请求和响应

假设我们有一个具有流式请求/响应参数的服务。例如,像这样的EchoService

  1. trait EchoService extends Service {
  2. def echo: ServiceCall[Source[String, NotUsed], Source[String, NotUsed]]
  3. override def descriptor = {
  4. import Service._
  5. named("echo").withCalls(
  6. call(echo)
  7. )
  8. }
  9. }

在编写测试时,Akka Streams TestKit 测试工具包非常有用。我们将Streams TestKit与Lagom ServiceTest实用程序结合使用:

  1. "The EchoService" should {
  2. "echo" in {
  3. // Use a source that never terminates (concat Source.maybe) so we
  4. // don't close the upstream, which would close the downstream
  5. val input = Source(List("msg1", "msg2", "msg3")).concat(Source.maybe)
  6. client.echo.invoke(input).map { output =>
  7. val probe = output.runWith(TestSink.probe(server.actorSystem))
  8. probe.request(10)
  9. probe.expectNext("msg1")
  10. probe.expectNext("msg2")
  11. probe.expectNext("msg3")
  12. probe.cancel
  13. succeed
  14. }
  15. }
  16. }

如何测试一个持久化实体

Persistent Entities可用于上述服务测试。除此之外,您还应该使用PersistentEntityTestDriver编写单元测试,它将在不使用数据库的情况下运行PersistentEntity
Persistent Entity 文档中有相关描述。