First, !
and ^
have been chosen with two criteria in mind:
- Reduce visual clutter
- Have relative precedence necessary to avoid parenthesis
What they do is: !
associates a description with a code that tests that description, and ^
concatenates a bunch of descriptions, tests, formatting annotations, and other supporting code into an object that describes the full test suite.
The point of "acceptance" specifications is they they are purely function. The mutable specifications take advantage of mutability to "register" code. For example, when you do this:
"a test" should {
"succeed" in {
success
}
}
That isn't associated with any declaration. It's just a statement in the body of class (the constructor), so how does Specs2 find out about it? Simple: when that code runs, it changes the value or a variable to register itself.
That's something that immutable specs do not do. The test is found by running the method is
, and that method has to return a list of all tests. Let's imagine a very OO, non syntactic sugar, way of doing that:
def is = List(
new TestCase("A Test", new TestRule("succeed", success)),
new TestCase("Another test", new TestRule("not fail", success))
)
So, what the method ^
does is joining "test cases" (so to speak -- not actual Specs2 terminology), and what !
does is associating a "test rule" to a "test case". It works a bit like below -- not exactly, since I'm changing stuff to make it look more like traditional OO.
"this is my specification" ^
"and example 1" ! e1^
"and example 2" ! e2
new ExampleDescription("this is my specification") ^
new ExampleDescription("and example 1") ! e1^
new ExampleDescription("and example 2") ! e2
new ExampleDescription("this is my specification") ^
new Example(new ExampleDescription("and example 1"), e1) ^
new Example(new ExampleDescription("and example 2"), e2)
new Fragment(
new ExampleDescription("this is my specification"),
List(
new Example(new ExampleDescription("and example 1"), e1),
new Example(new ExampleDescription("and example 2"), e2)
)
)
new Fragments(
new Fragment(
new ExampleDescription("this is my specification"),
List(
new Example(new ExampleDescription("and example 1"), e1),
new Example(new ExampleDescription("and example 2"), e2)
)
)
)
So, an acceptance specification is formed by fragments. A fragment can be composed in many ways, such as a description associated with some code, or a description followed by other fragments.
The way it works is really complex, to allow for diverse needs such as auto-examples, given-when-then specifications, regex extractors, partial functions, formatting of specification output, etc.