Scala 使用specs2设计复杂的工作流
开发功能测试我需要模拟一个工作流,其中一个步骤的结果将用作以下步骤的输入。例如:Scala 使用specs2设计复杂的工作流,scala,specs2,Scala,Specs2,开发功能测试我需要模拟一个工作流,其中一个步骤的结果将用作以下步骤的输入。例如: 搜索具有给定条件的酒店\房间 检查请求是否成功 检查是否至少有一些结果 从步骤1中选择随机房间 从第二步开始预订房间。 检查请求是否成功 从步骤3取消预订。 检查请求是否成功 这里的要点是: 我们不能执行3。不做1 我们不能执行4。不做3 如果某个步骤失败,我们应该中止该功能 为这种情况制定规范的方法是什么?最简单的方法是使用一个表示流程的可变对象和一个顺序规范: class HotelSpec e
- 检查请求是否成功
- 检查是否至少有一些结果
- 检查请求是否成功
- 检查请求是否成功
- 我们不能执行3。不做1
- 我们不能执行4。不做3
- 如果某个步骤失败,我们应该中止该功能
为这种情况制定规范的方法是什么?最简单的方法是使用一个表示流程的可变对象和一个
顺序规范:
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
来封装它,以避免可变状态,稍后我会尝试。谢谢您提供了一个很好的示例!我花了一段时间才意识到这一点,但毕竟我认为保持一个可变状态的必要性是正确的。感谢您的大力支持和非常有趣的lib!