Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala 如何设计Specs2数据库测试,以及相互依赖的测试?_Scala_Integration Testing_Specs2 - Fatal编程技术网

Scala 如何设计Specs2数据库测试,以及相互依赖的测试?

Scala 如何设计Specs2数据库测试,以及相互依赖的测试?,scala,integration-testing,specs2,Scala,Integration Testing,Specs2,有没有更好的方法来设计一个测试,有很多测试依赖于以前测试的结果 在下面,您可以找到我当前的测试套件。我不喜欢测试片段之间的vars。不过,它们是“需要的”,因为有些测试会生成后续测试重用的ID号 我应该将ID号存储在Specs2上下文中,还是创建一个单独的对象来保存所有可变状态?并且只在规范对象中放置测试片段?还是有更好的方法 如果某个测试失败,我想取消相同深度的剩余测试。我可以使测试片段相互依赖吗?(我知道我可以取消单个测试片段中剩余的匹配器(通过使用可变测试,或通过orSkip),但是取消整

有没有更好的方法来设计一个测试,有很多测试依赖于以前测试的结果

在下面,您可以找到我当前的测试套件。我不喜欢测试片段之间的
var
s。不过,它们是“需要的”,因为有些测试会生成后续测试重用的ID号

  • 我应该将ID号存储在Specs2上下文中,还是创建一个单独的对象来保存所有可变状态?并且只在规范对象中放置测试片段?还是有更好的方法

  • 如果某个测试失败,我想取消相同深度的剩余测试。我可以使测试片段相互依赖吗?(我知道我可以取消单个测试片段中剩余的匹配器(通过使用可变测试,或通过orSkip),但是取消整个片段呢?)

  • 声明如果出现故障,可以使用.orSkip跳过示例的其余部分

    但我没有亲自尝试过(关于问题1:我不知道示例中是否有更好的
    var
    s替代方案。也许我的示例太长了,也许我应该将我的规范分成许多更小的规范。)

    关于问题2,我发现停止后续测试可以这样做:

    "ex1" >> ok
    "ex2" >> ok
    "ex3" >> ko
     step(stopOnFail=true)
    
    "ex4" >> ok
    
    (如果ex1、ex2或ex3失败,则将跳过Ex4。(但是,如果使用的是顺序规范,则在规范2<1.12.3中无法按预期工作。)


    这是另一种方法:根据我们的经验,可以让后续测试在失败时停止,如下所示: (“示例2”将被跳过,但“示例3”和“4”将运行)

    您需要在
    build.sbt
    中编辑测试选项,这样子规范在包含之后就不会再次独立执行。通过电子邮件:

     testOptions := Seq(Tests.Filter(s =>
      Seq("Spec", "Selenium").exists(s.endsWith(_)) &&
        ! s.endsWith("ChildSpec")))
    

    您的问题有两个部分:使用vars存储中间状态,以及在失败时停止示例

    1-使用变量

    在使用可变规范时,有一些替代使用vars的方法

    您可以使用表示流程步骤的
    惰性VAL

    object DatabaseSpec extends mutable.Specification { 
      sequential
    
      "The Data Access Object" should {
    
        lazy val id1    = database.save(Entity(1))
        lazy val loaded = database.load(id1)
        lazy val list   = database.list
    
        "save an object"   >> { id1 === 1 }
        "load one object"  >> { loaded.id === id1 }
        "list all objects" >> { list === Seq(Entity(id1)) }
      }
    
      object database {
        def save(e: Entity) = e.id
        def load(id: Int) = Entity(id)
        def list = Seq(Entity(1))
      }
      case class Entity(id: Int)
    }
    
    由于这些值是惰性的,因此只有在执行示例时才会调用它们

    如果您准备更改当前规范的结构,您也可以使用最新的1.12.3-SNAPSHOT,并将所有这些小期望归为一个示例:

    "The Data Access Object provides a save/load/list api to the database" >> {
    
      lazy val id1    = database.save(Entity(1))
      lazy val loaded = database.load(id1)
      lazy val list   = database.list
    
      "an object can be saved"  ==> { id1 === 1 }
      "an object can be loaded" ==> { loaded.id === id1 }
      "the list of all objects can be retrieved" ==> {
        list === Seq(Entity(id1))
      }
    }
    
    如果这些期望中的任何一个失败,那么其余的将不会执行,您将收到失败消息,如:

    x The Data Access Object provides a save/load/list api to the database
      an object can not be saved because '1' is not equal to '2' (DatabaseSpec.scala:16)
    
    另一种可能需要两个小的改进,即使用编写规范的方式,但在
    给定的
    步骤中使用“抛出”期望。正如您在《用户指南》中所看到的,
    Given/When/Then
    步骤从字符串中提取数据,并将键入的信息传递给下一个
    Given/When/Then

    import org.specs2._
    import specification._
    import matcher.ThrownExpectations
    
    class DatabaseSpec extends Specification with ThrownExpectations { def is = 
      "The Data Access Object should"^
        "save an object"             ^ save^
        "load one object"            ^ load^
        "list all objects"           ^ list^
      end
    
      val save: Given[Int] = groupAs(".*") and { (s: String) =>
        database.save(Entity(1)) === 1
        1
      }
    
      val load: When[Int, Int] =  groupAs(".*") and { (id: Int) => (s: String) =>
        val e = database.load(id)
        e.id === 1
        e.id
      }
    
      val list: Then[Int] =  groupAs(".*") then { (id: Int) => (s: String) =>
        val es = database.list
        es must have size(1)
        es.head.id === id
      }
    }
    
    我将要做的改进是:

    • 捕获失败异常以将其报告为失败而不是错误
    • 当字符串描述中没有可提取的内容时,不再需要使用
      groupAs(“.*”)和
      
      
    在这种情况下,写下以下内容就足够了:

    val save: Given[Int] = groupAs(".*") and { (s: String) =>
      database.save(Entity(1)) === 1
      1
    }
    
    另一种可能是允许直接写:

    val save: Given[Int] = groupAs(".*") and { (s: String) =>
      database.save(Entity(1)) === 1
    }
    
    其中,由于
    MatchResult[T]
    对象已包含类型为
    T
    的值,因此可以从
    String=>MatchResult[T]
    创建
    给定的[T]
    对象,该对象将成为“给定”

    2-在示例失败后停止执行

    在上下文中使用隐式的
    WhenFail
    当然是做你想做的事情的最佳方式(除非你按照上面G/W/T示例所示的期望描述)

    关于
    步骤的注释(stepOnFail=true)


    步骤(stepOnFail=true)
    的工作原理是,如果上一个并发示例块中的一个示例失败,则中断以下示例。但是,当您使用
    顺序
    时,前面的块仅限于一个示例。这就是你所看到的。实际上,我认为这是一个bug,不管您是否使用sequential,都不应该执行所有剩余的示例。因此,请继续关注本周末即将到来的修复。

    实际上,这只会取消当前
    {…}
    块中的剩余测试(我认为这被称为“测试片段”)。我要寻找的是在同一深度上消除所有后续测试片段的东西(我的意思是,文本“第二个示例…”开始的“深度”,而不是
    {…}
    块中的“深度”)。我将更新我的问题,使其更加清晰。使用带有1.12.3-SNAPSHOT的
    ==>
    将期望分组到一个示例中似乎很好。我认为生成的代码相当容易阅读。此外,将所有的惰性VAL放在测试代码之上,我认为代码更容易阅读我可能会在接下来的几个月里,在接下来的几个月里重写一点测试套件(当时已经发布了1.123),并考虑使用<代码>=> < />代码。(我还将它分成许多较小的测试套件,并
    包括它们。)感谢关于
    步骤(stepOnFail=true)
    的注释。所有更改都在1.12.3-SNAPSHOT中:现在可以从给定的/When/Then步骤抛出失败,可以直接从使用完整描述字符串的函数创建给定步骤,即步骤(stopOnFail=true)在顺序规范中的行为与预期一致。顺便说一句,specs2 3.x的设计就是为了解决这个问题,您可以根据以前的测试结果创建任意测试。请参见此处:
    x The Data Access Object provides a save/load/list api to the database
      an object can not be saved because '1' is not equal to '2' (DatabaseSpec.scala:16)
    
    import org.specs2._
    import specification._
    import matcher.ThrownExpectations
    
    class DatabaseSpec extends Specification with ThrownExpectations { def is = 
      "The Data Access Object should"^
        "save an object"             ^ save^
        "load one object"            ^ load^
        "list all objects"           ^ list^
      end
    
      val save: Given[Int] = groupAs(".*") and { (s: String) =>
        database.save(Entity(1)) === 1
        1
      }
    
      val load: When[Int, Int] =  groupAs(".*") and { (id: Int) => (s: String) =>
        val e = database.load(id)
        e.id === 1
        e.id
      }
    
      val list: Then[Int] =  groupAs(".*") then { (id: Int) => (s: String) =>
        val es = database.list
        es must have size(1)
        es.head.id === id
      }
    }
    
    val save: Given[Int] = groupAs(".*") and { (s: String) =>
      database.save(Entity(1)) === 1
      1
    }
    
    val save: Given[Int] = groupAs(".*") and { (s: String) =>
      database.save(Entity(1)) === 1
    }