隐式转换器是否可以使用按名称参数?

时间:2017-06-02 16:59:28

标签: scala

我想简化我的ScalaTest套件的代码。 我的大多数测试都有一个生成一些Assertion - s的主体,然后还需要执行一些清理,这在概念上是副作用,但是如果这些清理中的一些产生异常我想要通过测试失败除了那个例外。 所以在我开始简化测试之前,看起来如下所示:

"Admin" should "be able to create a new team" in{
    val attempt=Try{
      When("Admin opens the Teams view")
      TeamsPage.open
      And("creates a new team")
      TeamsPage.createNewTeam(tempTeam)
      Then("this team is shown in the list")
      TeamsPage.isParticularTeamShownInTeamList(tempTeam.name) shouldBe true
    }
    val cleanUp = Try(TeamsPage.cleanUpTeam(tempTeam))
    attempt.flatMap(r => cleanUp.map(_ => r)).get
}

相当不错,但我希望有更少的样板。所以我从这样的事情开始:

class FollowUp(block: => Assertion){
  def andThen[T](followUp: =>T):Assertion = {
    val start = Try(block)
    val followUpAttempt = Try(followUp)
    start.flatMap(r => followUpAttempt.map(_ => r)).get
  }
}

object FollowUp{
  implicit def assertionToFollowUp(a: => Assertion):FollowUp = new FollowUp(a)
}


class TeamManagementTest extends ADMPSuite with AbilityToManageUsers{
  import FollowUp._

  val tempTeam = Team("Temp QA Team")

  "Admin" should "be able to create a new team" in{
    {
      When("Admin opens the Teams view")
      TeamsPage.open
      And("creates a new team")
      TeamsPage.createNewTeam(tempTeam)
      Then("this team is shown in the list")
      TeamsPage.isParticularTeamShownInTeamList(tempTeam.name) shouldBe false
    } andThen TeamsPage.cleanUpTeam(tempTeam)
  }
}

正如你所看到的,我的想法是从一个简单的组合器andThen开始,这将允许我用一个副作用跟进我的测试体。我希望按名称传递测试体,因此在它被包装到Try()之前它不会开始执行。这是必需的,因为即使在测试体失败或产生错误的情况下,我也需要执行后续副作用。 所以我已经声明了一个带有by-name参数的隐式转换器。 但它没有编译并且告诉我这个:

Error:(49, 42) type mismatch;
 found   : Boolean
 required: PartialFunction[scala.util.Try[org.scalatest.compatible.Assertion],?]
    attempt andThen TeamsPage.cleanUpTeam(tempTeam)
                                         ^

我不明白为什么会这样。 如果我将参数更改为按值

object FollowUp{
  implicit def assertionToFollowUp(a: Assertion):FollowUp = new FollowUp(a)
}

然后代码编译,但它当然不会在测试主体失败或产生异常的情况下应用后续。

你能建议如何以一种很好的方式解决这个问题吗?

3 个答案:

答案 0 :(得分:2)

您可以使用ScalaTest称之为贷款夹具的内容:

def withTeamsPage[A](body: Team => A) = {
  val tempTeam = Team("Temp QA Team")
  try {
    body(tempTeam)
  } finally {
    TeamsPage.cleanUpTeam(tempTeam)
  }
}

"Admin" should "be able to create a new team" in withTeamsPage { tempTeam =>
  When("Admin opens the Teams view")
  TeamsPage.open
  And("creates a new team")
  TeamsPage.createNewTeam(tempTeam)
  Then("this team is shown in the list")
  TeamsPage.isParticularTeamShownInTeamList(tempTeam.name) shouldBe false   
}

答案 1 :(得分:1)

正如我在评论中所述,问题出在方法andThen的名称中。它发生在同一个方法是PartialFuction类的成员,在这种情况下编译器决定我试图在部分函数上调用它。在我重命名方法后,所有内容都已编译完成。

答案 2 :(得分:1)

它对我有用,但也许你的测试还会有其他事情发生。

我不会使用ScalaTest,除了一两个SO问题,所以有一点点盐:

package testy

import scala.language.implicitConversions
import scala.util.Try
import org.scalatest._
import Matchers._

trait Cleaner {
  def cleanUp(): Unit = println("cleaning...")
}

class FollowUp(block: => Assertion) {
  println("deferring...")
  def andThen[T](followUp: => T): Assertion = {
    println("evaluate...")
    val start = Try(block)
    println("followup...")
    val followUpAttempt = Try(followUp)
    start.flatMap(r => followUpAttempt.map(_ => r)).get
  }
}

object FollowUp{
  implicit def assertionToFollowUp(a: => Assertion): FollowUp = new FollowUp(a)
}

import FollowUp._

class CSpec extends FlatSpec with Cleaner {
  "A zero size Set" should "have size 0" in {
    assert(Set.empty.size == 0)
  }

  "An empty Set" should "have size 0" in {
    {
      println("test...")
      Set.empty.isEmpty shouldBe true
    } andThen cleanUp()
  }
}

产生输出:

deferring...
evaluate...
test...
followup...
cleaning...
[info] CSpec:
[info] A zero size Set
[info] - should have size 0
[info] An empty Set
[info] - should have size 0
[info] Run completed in 233 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 1 s, completed Jun 2, 2017 5:11:16 PM

-Xprint:typer显示转化

    CSpec.this.convertToInAndIgnoreMethods(org.scalatest.Matchers.convertToStringShouldWrapper("An empty Set")(org.scalactic.source.Position.apply("CSpec.scala", "Please set the environment variable SCALACTIC_FILL_FILE_PATHNAMES to yes at compile time to enable this feature.", 34), scalactic.this.Prettifier.default).should("have size 0")(CSpec.this.shorthandTestRegistrationFunction)).in(FollowUp.assertionToFollowUp({
  scala.Predef.println("test...");
  org.scalatest.Matchers.convertToAnyShouldWrapper[Boolean](scala.Predef.Set.empty[Nothing].isEmpty)(org.scalactic.source.Position.apply("CSpec.scala", "Please set the environment variable SCALACTIC_FILL_FILE_PATHNAMES to yes at compile time to enable this feature.", 37), scalactic.this.Prettifier.default).shouldBe(true)
}).andThen[Unit](CSpec.this.cleanUp()))(org.scalactic.source.Position.apply("CSpec.scala", "Please set the environment variable SCALACTIC_FILL_FILE_PATHNAMES to yes at compile time to enable this feature.", 34))

这演示了将块的结果表达式转换为期望的类型,以及将块表达式转换为具有所需成员的类型之间的区别:

$ scala -language:_
Welcome to Scala 2.12.2 (OpenJDK 64-Bit Server VM, Java 1.8.0_131).
Type in expressions for evaluation. Or try :help.

scala> case class C(c: Int)
defined class C

scala> implicit def cc(i: => Int): C = C(42)
cc: (i: => Int)C

scala> 5.c
res0: Int = 42

scala> def f(c: C) = ()
f: (c: C)Unit

scala> f(5)

scala> f { println("effing") ; 5 }
effing

scala> { println("effing") ; 5 }.c
res3: Int = 42

build.sbt:

scalaVersion := "2.12.2"

scalacOptions ++= "-Xlint" :: "-Xprint:typer" :: Nil

libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test"

编辑:

您似乎正在使用Scalatest的异步工具。 AsyncFlatSpec具有从热切值到Future的隐式转换。 Future有一个andThen方法,需要PartialFunction

[error] /home/amarki/tmp/testy/src/test/scala/CSpec.scala:41: type mismatch;
[error]  found   : Unit
[error]  required: PartialFunction[scala.util.Try[org.scalatest.compatible.Assertion],?]
[error]     } andThen cleanUp()
[error]                      ^
[warn] /home/amarki/tmp/testy/src/test/scala/CSpec.scala:27: Unused import
[warn] import FollowUp._
[warn]                 ^
[warn] one warning found
[error] one error found

这解释了为什么转换参数是按名称的,这一点很重要。编译器首先查找按值转换。

scaladoc谈论固定装置大约在长页的一半。还有examples

他们建议:

trait Resourceful { _: fixture.AsyncFlatSpec with Cleaner =>

  type FixtureParam = String

  def withFixture(test: OneArgAsyncTest): FutureOutcome = { 
    complete {
      withFixture(test.toNoArgAsyncTest("hello, world"))
    } lastly {
      cleanUp()
    }
  }
}

class CSpec extends fixture.AsyncFlatSpec with Resourceful with Cleaner {
  "An eager zero size Set" should "have size 0" in { () =>
    {
      println("test one...")
      Set.empty.isEmpty shouldBe true
    } 
  }

  "An empty Set" should "have size 0" in { s =>
    Future {
      println(s"testing $s...")
      Set.empty.isEmpty shouldBe true
    } 
  }
}

投掷lastly未通过测试。