В вашем вопросе есть две части: использование vars для хранения промежуточного состояния и остановка примеров при сбое.
1 - Использование переменных
Есть несколько альтернатив использованию vars при использовании изменяемой спецификации.
Вы можете использовать lazy vals
, представляющий шаги вашего процесса:
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
шагов. Как видно из Руководства пользователя, 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(".*") and
, когда нечего извлекать из описания строки.
В этом случае достаточно написать:
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
}
где объект Given[T]
может быть создан из String => MatchResult[T]
, потому что объект MatchResult[T]
уже содержит значение типа T
, которое станет "данным".
2 - Остановить выполнение после неудачного примера
Использование неявного контекста WhenFail
Around
, безусловно, лучший способ сделать то, что вы хотите (если вы не используете описания ожиданий, как показано выше в примере G/W/T).
Примечание к step(stepOnFail = true)
step(stepOnFail = true)
работает, прерывая следующие примеры, если один пример в предыдущем блоке параллельных примеров дал сбой. Однако, когда вы используете sequential
, этот предыдущий блок ограничен только одним примером. Отсюда то, что вы видите. На самом деле я думаю, что это ошибка и что все оставшиеся примеры не должны выполняться независимо от того, используете ли вы последовательный код или нет. Так что следите за исправлением, которое выйдет на этих выходных.
person
Eric
schedule
02.11.2012