
org.specs.specification.SpecificationStructure.scala Maven / Gradle / Ivy
package org.specs.specification
import org.specs.matcher.MatcherUtils._
import org.specs.SpecUtils._
/**
* This trait provides a structure to a specification.
* A specification is composed of:
* - sub specifications or
*
- systems under tests (suts)
*
- examples which are components of systems under tests
*
- sub-examples which are components of examples
*
* A specification is also given a description which is formed from its class name by default
* but which can be also overriden
*
* A specification can be composed of other specifications:
* "A complex specification".isSpecifiedBy(spec1, spec2)
* or declare("A complex specification").isSpecifiedBy(spec1, spec2)
*
* A system under test can be created from a string with an implicit definition using should
:
* "my system under test" should {}
* Alternatively, it could be created with:
* specify("my system under test").should {}
*
* Sub-examples can be created by declaring them inside the current example:
* def otherExample = "this is a shared example" in { "this assertion" must notBeEmpty }
*
* "behave like other examples" in {
* otherExample
* }
* Sub-examples are usually used to share examples across specifications (see the Stack example in test/scala/scala/specs/sample)
*
* A SpecificationStructure
also implements an ExampleLifeCycle
trait
* allowing subclasses to refine the behaviour of the specification before/after an example and before/after
* a test inside an example. This is used to plug setup/teardown behaviour at the sut level and to plug
* mock expectations checking when a specification is using the Mocker trait: mySpec extends Specification with Mocker
*/
trait SpecificationStructure extends ExampleLifeCycle {
/** description of the specification */
var description = createDescription(getClass.getName)
/** name of the specification */
var name = createDescription(getClass.getName)
/**
* @return a description from the class name, taking the last name which doesn't contain a $ or a number.
* For example: com.pack1.MyClass$1$ will:
* - split on $ and reverse: [1, com.pack1.MyClass]
*
- drop the every element which is an integer -> [com.pack1.MyClass]
*
- take the first element: com.pack1.MyClass
*
- split on . and reverse: [MyClass, pack1, com]
*
- take the last element: MyClass
*/
def createDescription(s: String) = s.
split("\\$").reverse.
dropWhile(isInteger(_))(0).
split("\\.").
reverse.toList(0)
/** specifications contained by the current specification. An empty list by default */
var subSpecifications: List[Specification] = Nil
/** this declares that a specification is composed of other specifications */
def isSpecifiedBy(specifications: Specification*) = {
this.description = this.name + " is specified by"
subSpecifications = subSpecifications:::specifications.toList
}
/** alias for isSpecifiedBy */
def areSpecifiedBy(specifications: Specification*) = {
this.description = this.name + " are specified by"
subSpecifications = subSpecifications:::specifications.toList
}
/**
* implicit definition allowing to declare a composition inside the current specification:
* "A complex specification".isSpecifiedBy(spec1, spec2)
*/
implicit def declare(d: String): SpecificationStructure = { name = d; this }
/** list of systems under test */
var suts : List[Sut] = Nil
/**
* implicit definition allowing to declare a new system under test described by a string desc
* Usage: "my system under test" should {}
* Alternatively, it could be created with:
* specify("my system under test").should {}
*/
implicit def specify(desc: String): Sut = {
suts = suts:::List(new Sut(desc, this))
if (this.isSequential)
suts.last.setSequential
suts.last
}
/** utility method to track the last sut being currently defined, in order to be able to add examples to it */
protected[this] def currentSut = if (!suts.isEmpty) suts.last else specify("The system")
/**
* implicit definition allowing to declare a new example described by a string desc
* Usage: "return 0 when asked for (0+0)" in {...}
* Alternatively, it could be created with:
* forExample("return 0 when asked for (0+0)").in {...}
*/
implicit def forExample(desc: String): Example = {
val newExample = new Example(desc, currentSut)
exampleContainer.addExample(newExample)
newExample
}
/**
* utility method to track the last example list being currently defined.
* It is either the list of examples associated with the current sut, or
* the list of subexamples of the current example being defined
*/
protected[this] def exampleContainer: Any {def addExample(e: Example)} = {
example match {
case Some(e) => e
case None => currentSut
}
}
}
/**
* This abstract trait is used to represent how examples should be executed:
* - sequentially or not ("not" is the default)
*
- with functions being executed before / after the example
*
- with functions being executed before / after the example tests
*
*/
trait ExampleLifeCycle {
protected var sequential = false
def isSequential = sequential
def setSequential = sequential = true
protected[this] var example: Option[Example] = None
def beforeExample(ex: Example) = {}
def beforeTest(ex: Example)= {}
def afterTest(ex: Example) = {}
def executeTest(ex: Example, t: =>Any) = {example = Some(ex); t}
def afterExample(ex: Example) = { example = None }
}