【问题标题】:Designing complex workflow with specs2使用 specs2 设计复杂的工作流程
【发布时间】:2013-09-09 18:52:55
【问题描述】:

开发功能测试我需要模拟一个工作流程,其中一个步骤的结果被用作以下步骤的输入。例子是这样的:

  1. 根据给定条件搜索酒店\房间
    • 检查请求是否成功
    • 检查至少有一些结果
  2. 从步骤 1 中选择随机房间。
  3. 从第 2 步预订房间。
    • 检查请求是否成功
  4. 从第 3 步取消预订。
    • 检查请求是否成功

这里的重点是:

  • 如果不执行 1,我们就无法执行 3。
  • 如果不执行 3,我们就无法执行 4。
  • 如果某个步骤失败,我们应该中止该功能

为这种情况制定规范的方法是什么?

【问题讨论】:

    标签: scala specs2


    【解决方案1】:

    最简单的做法是拥有一个表示进程的可变对象和一个sequential 规范:

    class HotelSpec extends mutable.Specification { sequential
      val hotel = new HotelProcess
    
      "get a room available on Monday" >> ifHotelOk {
        val rooms = request(MONDAY)
        hotel.selectedRooms = rooms
        rooms must not beEmpty
      }
    
      "book the room" >> ifHotelOk {
        val booking = bookRoom(hotel.selectedRooms.head)
        hotel.currentBooking = booking
        booking must beOk
      }
    
      def ifHotelOk(r: =>Any) = if (hotel.canContinueProcess) {
        try { r; hotel.continueProcess }
        catch { case t: Throwable => hotel.stopProcess; throw t }
      } else skipped("hotel process error in previous steps")
    }
    

    [更新]

    这是另一种更好地封装 var 的方法:

    import org.specs2._
    import org.specs2.execute._
    import org.specs2.specification.FixtureExample
    
    class HotelSpec extends HotelProcessSpec {
      "get a room available on Monday" >> { hotel: HP =>
        val rooms = request(MONDAY)
        rooms must be empty
    
        // update the state of the process at the end of the example
        hotel.selectedRoomsAre(rooms)
      }
    
      // this example will only execute if the previous step was ok
      "book the room" >> { hotel: HP =>
        val booking = bookRoom(hotel.selectedRooms.head)
        booking.booked must beTrue
      }
    
      val MONDAY = "monday"
      def request(day: String): Seq[Room] = Seq(Room())
      def bookRoom(room: Room) = Booking()
    }
    
    /**
     * A specification trait encapsulating the process of booking hotel rooms
     */
    trait HotelProcessSpec extends mutable.Specification with FixtureExample[HotelProcess] {
      sequential
    
      type HP = HotelProcess
      private var hotelProcess = HotelProcess()
    
      // if the hotelProcess is returned as the last statement of an Example
      // set the new value of the hotelProcess and return Success
      implicit def hotelProcessAsResult: AsResult[HotelProcess] = new AsResult[HotelProcess] {
        def asResult(hp: =>HotelProcess) =
          try { hotelProcess = hp; Success() }
          catch { case t: Throwable => hotelProcess = hotelProcess.stop; throw t }
      }
    
      /**
       * stop executing examples if one previous step failed
       */
      protected def fixture[R : AsResult](f: HotelProcess => R): Result = {
        if (hotelProcess.continue) {
          val result = AsResult(f(hotelProcess))
          if (!result.isSuccess) hotelProcess = hotelProcess.stop
          result
        }
        else                       skipped(" - SKIPPED: can't execute this step")
      }
    
    }
    
    case class HotelProcess(selectedRooms: Seq[Room] = Seq(), continue: Boolean = true) {
      def stop = copy(continue = false)
      def selectedRoomsAre(rooms: Seq[Room]) = copy(selectedRooms = rooms)
    }
    case class Room(number: Int = 0)
    case class Booking(booked: Boolean = true)
    

    【讨论】:

    • 我认为应该可以用Fixture 封装它以避免可变状态,我稍后会尝试。
    • 谢谢你的好例子!我花了一段时间才意识到这一点,但毕竟我认为保持可变状态的必要性是正确的。感谢您的大力支持和非常有趣的库!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-23
    • 2011-03-02
    • 1970-01-01
    • 2014-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多