This page explains the overall design of specs2:
note: some details might not be completely up to date as the code base evolves.
The structure of a specification is very simple, it is just a list of Fragments
provided by the is
method of theSpecificationStructure
trait:
+---------------+ 1..n +-----------+
| Specification | ------------------------> | Fragment |
+---------------+ +-----------+
^
|
+----------+-----------+-----------+-------------+-------------+---------------+
| | | | | | |
+------+ +---------+ +-------+ +---------+ +-----------+ +---------+ +-----------------+
| Text | | Example | | Step | | Action | | SpecStart | | SpecEnd | | TaggingFragment |
+------+ +---------+ +-------+ +---------+ +-----------+ +---------+ +-----------------+
Here's a short description of all the Fragments:
There are implicits to create Fragments (found in the org.specs2.specification.FragmentsBuilder
trait):
String => Text
, to create a simple Text FragmentString ! Result => Example
, to create an ExampleOnce built, these Fragments can be "linked" with ^
, creating a Fragments
object, containing a Seq[Fragment]
:
val fragments: Fragments =
"this text" ^
"is related to this Example" ! success
The Fragments
object is used to hold temporarily a sequence of Fragments as it is built and it makes sure that when the building is done, the Fragments passed for execution will start and end with proper SpecStart and SpecEnd fragments.
In a mutable Specification there is no visible "link" between Fragments, they're all created and linked through side-effects (thanks to an enhanced version of the FragmentsBuilder
trait in the org.specs2.mutable
package):
// build an Example and add it to the specFragments variable
"this example must succeed" in { success }
"same thing here" in { success }
Of course this there is mutation involved here, it's not advised to do anything concurrent at that point.
The execution is triggered by the various reporters and goes through 5 steps:
// code from the Reporter trait
spec.content |> select |> sequence |> execute |> store |> export(spec)
Selection: the Fragments are filtered according to the Arguments object. In that phase all examples but a few can be filtered if the only("this example")
option is used for instance. Another way to select fragments is to insert TaggingFragment
s inside the specification.
If the isolated
argument is true, each example body is replaced with the same body executing in a cloned Specification to avoid seeing side-effects on local variables.
Sequencing: the Fragments are sorted in groups so that all the elements of a group can be executed concurrently. This usually why Steps are used. If my fragments are: fragments1 ^ step ^ fragments2
then all fragments1 will be executed,
then step, then fragments2.
Execution: for each group, the execution of the fragments is concurrent by default and results are collected in
a sequence of ExecutingFragments
. We don't wait for the execution of all the Fragments to be finished before starting the reporting.
Storing: after an execution we compute the statistics for each specification and store the results in a file (specs2-reports/specs2.stats
).
This allows to do consequent runs based on previous executions: to execute failed specifications only or to create the index page with an indicator of previously executed specifications
Exporting: depending on the exporter, the ExecutedFragments are translated to PrintLines
or HtmlLines
to be flushed out to the console or in an html file
All the reporters start with a sequence of ExecutingFragment
s. A list of Reducers
is used to collect relevant information:
One of the main difficulties in this 'reduction' is the fact that included specifications change the context of what needs to be accumulated. The reporter.NestedBlocks
trait provides functions to handle this.
Then, each fragment and associated data (level, statistics, arguments,...) is translated to a display element:
PrintLines
: PrintSpecStart
, PrintText
, PrintResult
,...HtmlLines
: HtmlSpecStart
, HtmlText
, HtmlResult
,...Description
objects with the corresponding code to execute (in JUnit the DescriptionsThe following package dependencies should be always verified, from low-level packages to high-level ones, where no package on a low layer can depend on a package on a higher layer:
+ runner
+ reporter
+ mutable specification
+ mock form
+ matcher
+ execute
+ analysis reflect xml html time json
+ collection control io text main data