Some initial documentation for StructureDSL

This commit is contained in:
Michael Miller 2018-11-03 12:13:53 -06:00
parent f6d6c859e6
commit 0a218b7e9f
1 changed files with 144 additions and 0 deletions

View File

@ -1,6 +1,150 @@
require "../example_group" require "../example_group"
module Spectator::DSL module Spectator::DSL
# Domain specific language for the main structure of a spec.
# The primary components of this are `#describe`, `#context`, and `#it`.
#
# These macros define modules and classes.
# Those modules and classes are used to create the test cases.
#
# A class is created for every block of code that contains test code.
# An `#it` block creates a class derived from `RunnableExample`.
# A `#pending` block creates a class derived from `PendingExample`.
# The classes are built so that they run the example's code when invoked.
# However, the actual example code is placed into a separate "wrapper" class.
# This is done to avoid overlap with the Spectator namespace.
# The example code ends up having visibility only into itself and the DSL.
#
# Here's some skeleton code to demonstrate this:
# ```
# it "does something" do
# # Test code goes here...
# end
#
# # becomes...
#
# # Class describing the example
# # and provides a means of running the test.
# # Typically every class, module, and method
# # that the user might see or be able to reference is obscured.
# # Fresh variables from Crystal's macros are used to achive this.
# # It makes debugging Spectator more difficult,
# # but prevents name collision with user code.
# class Example123 < RunnableExample
# def initialize(group, sample_values)
# # group and sample_values are covered later.
# super
# @wrapper = Wrapper123.new(sample_values)
# end
#
# # Returns the text provided by the user.
# # This isn't stored as a member
# # so that it can be referenced directly in compiled code.
# def what
# "does something"
# end
#
# # This method is called by `RunnableExample`
# # when the example code should be ran.
# def run_instance
# @wrapper._run123
# end
# end
#
# # Wrapper class for the example code.
# # This isolates it from Spectator's internals.
# class Wrapper123
# include Context123 # More on this in a bit.
# include ExampleDSL # Include DSL for the example code.
#
# # Generated method name to avoid conflicts.
# def _run123
# # Test code goes here...
# end
# end
# ```
#
# Modules are used to provide context and share methods across examples.
# They are used as mix-ins for the example code.
# The example code wrapper class includes its parent module.
# This allows the example to access anything that was defined in the same context.
# Contexts can be nested, and this is achieved by including the parent module.
# Whenever a module or class is defined,
# it includes its parent so that functionality can be inherited.
#
# For example:
# ```
# describe "#foo" do
# subject { described_class.foo(value) }
#
# context "when given :bar" do
# let(value) { :bar }
#
# it "does something" do
# # ...
# end
# end
# end
#
# # becomes...
#
# # describe "#foo"
# module Group123
#
# # Start a new group.
# # More on this in a bit.
# Builder.start_group("#foo")
#
# def subject
# described_class.foo(value)
# end
#
# # context "when given :bar"
# module Group456
# include Group123 # Inherit parent module.
#
# # Start a nested group.
# Builder.start_group("when given :bar")
#
# def value
# :bar
# end
#
# # Wrapper class for the test case.
# class Wrapper456
# include Group456 # Include context.
#
# # Rest of example code...
# end
#
# # Example class for the test case.
# class Example456 < RunnableExample
# # Rest of example code...
# end
#
# # Add example to group.
# Builder.add_example(Example456)
#
# # End "when given :bar" group.
# Builder.end_group
#
# end
#
# # End "#foo" group.
# Builder.end_group
#
# end
# ```
#
# In addition to providing modules as mix-ins,
# example groups are defined with `#describe` and `#context`.
# The DSL makes use of `Builder` to construct the run-time portion of the spec.
# As groups are defined, they are pushed on a stack
# and popped off after everything nested in them is defined.
# `Builder` tracks the current group (top of the stack).
# This way, examples, hooks, nested groups, and other items can be added to it.
# Groups and examples are nested in a parent group.
# The only group that isn't nested is the root group - `RootExampleGroup`.
module StructureDSL module StructureDSL
def initialize(sample_values : Internals::SampleValues) def initialize(sample_values : Internals::SampleValues)
end end