2017-06-02 36 views
0

我想简化我的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) 
} 

则代码编译,但它当然不适用的情况下,测试体失败或产生异常的随访。

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

+1

虽然@ephemient解决方案是好的,你可能应该使用它,它看起来像你对你的问题的根本原因感兴趣,但我不能用你的例子重现它。如果我用一些存根填充它的简化版本,它会为我编译(请参阅[完整代码](https://pastebin.com/DdSuS3Tw)我试过了)。那么你能否提供一个[MCVE](https://stackoverflow.com/help/mcve)? – SergGr

+0

明天尝试提取MCVE,虽然它可能不那么容易:) –

+2

我得到了问题所在。我将“andThen”重新命名为“followWith”,然后在两种情况下,当测试主体产生成功的断言和失败时,编译和按预期工作。在思考过错误中提到的“PartialFunction”与我的情况无关之后,我意识到'andThen'是'PartialFunction'的一种方法,所以应该有一些命名阴影或其他东西。所以如果我给这个函数命名,甚至可以使用“alex”。 –

回答

1

正如我在评论这个问题是在方法andThen名如上所述。它发生了,所以相同的方法是PartialFuction类的成员,在这种情况下,编译器决定我试图在部分函数上调用它。在我将这个方法重新编译和工作之后,

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 
} 
+0

ephemient,谢谢你的建议。稍后我会在笔记本电脑上试一试。不知道它是否会套用,因为我只提供了一个最简单的测试样本。在我的套件中,我通常有几十个测试,其中一些测试非常复杂,使用数据库或API调用等初始化不同的实体。但无论如何,我应该使用这种贷款模式。 同时,你有什么想法为什么在隐式转换器中的名字参数不能编译? –

+0

@AlexanderArendar基本上,当你尝试应用转换时,你有一个块(值)而不是thunk(名称)。我发现很难预测什么时候会发生:(https://issues.scala-lang.org/browse/SI-3237 – ephemient

+0

我甚至试图将该块声明为lazy val,但这并没有帮助。在转换发生之前,当前代码段中的代码块会被计算出来,代码在我看来 - 它应该只在转换后才能计算出来 –

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)) 

这证明了转换结果表达式的区别k转换为预期类型,并将块表达式转换为具有所需成员的类型:

$ 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 

构建。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投掷失败的考验。

+0

我不知道这个答案是什么。我看到了OP的评论,“我得到了问题所在,我将”and Then“改名为”followWith“,然后编译并按预期工作。” – SergGr

+0

@SergGr OP没有证明错误,我试图再现作品,也许还有其他类型/暗示在玩,但“改名”并不令人满意,底部的例子使用名义含义来解决问题。 –

+0

谢谢,som-snytt。对我来说,了解我之前没有经历过的Xlint选项是有用的。关于错误的消息,我确实在答案原始描述中显示了编译器错误输出。 –

相关问题