2017-06-02 35 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選項是有用的。關於錯誤的消息,我確實在答案原始描述中顯示了編譯器錯誤輸出。 –

相關問題