Restructure to use a composite design pattern

Examples and example groups now have a common ancestor.
This commit is contained in:
Michael Miller 2018-10-14 17:10:12 -06:00
parent 2070d7816b
commit 30a45a24d3
10 changed files with 134 additions and 72 deletions

View file

@ -2,7 +2,7 @@ module Spectator::DSL
module Builder
extend self
@@group_stack = Array(ExampleGroupBuilder).new(1, root_group)
@@group_stack = Array(NestedExampleGroupBuilder).new(1, root_group)
private class_getter root_group = RootExampleGroupBuilder.new()
@ -10,13 +10,13 @@ module Spectator::DSL
@@group_stack.last
end
private def push_group(group : ExampleGroupBuilder)
private def push_group(group : NestedExampleGroupBuilder)
current_group.add_child(group)
@@group_stack.push(group)
end
def start_group(*args) : Nil
group = ::Spectator::DSL::ExampleGroupBuilder.new(*args)
group = ::Spectator::DSL::NestedExampleGroupBuilder.new(*args)
push_group(group)
end

View file

@ -1,24 +1,24 @@
require "./example_group_builder"
require "./nested_example_group_builder"
module Spectator::DSL
class GivenExampleGroupBuilder(T) < ExampleGroupBuilder
class GivenExampleGroupBuilder(T) < NestedExampleGroupBuilder
def initialize(what : String, @collection : Array(T), @symbol : Symbol)
super(what)
end
def build(parent : ExampleGroup?, sample_values : Internals::SampleValues) : ExampleGroup
ExampleGroup.new(@what, parent, build_hooks).tap do |group|
def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : NestedExampleGroup
NestedExampleGroup.new(@what, parent, build_hooks).tap do |group|
group.children = @collection.map do |value|
build_sub_group(group, sample_values, value).as(ExampleGroup::Child)
build_sub_group(group, sample_values, value).as(ExampleComponent)
end
end
end
private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : ExampleGroup
private def build_sub_group(parent : ExampleGroup, sample_values : Internals::SampleValues, value : T) : NestedExampleGroup
sub_values = sample_values.add(@symbol, @symbol.to_s, value)
ExampleGroup.new(value.to_s, parent, ExampleHooks.empty).tap do |group|
NestedExampleGroup.new(value.to_s, parent, ExampleHooks.empty).tap do |group|
group.children = @children.map do |child|
child.build(group, sub_values).as(ExampleGroup::Child)
child.build(group, sub_values).as(ExampleComponent)
end
end
end

View file

@ -1,6 +1,6 @@
module Spectator::DSL
class ExampleGroupBuilder
alias Child = ExampleFactory | ExampleGroupBuilder
class NestedExampleGroupBuilder
alias Child = ExampleFactory | NestedExampleGroupBuilder
@children = [] of Child
@before_all_hooks = [] of ->
@ -36,10 +36,10 @@ module Spectator::DSL
@around_each_hooks << block
end
def build(parent : ExampleGroup?, sample_values : Internals::SampleValues) : ExampleGroup
ExampleGroup.new(@what, parent, build_hooks).tap do |group|
def build(parent : ExampleGroup, sample_values : Internals::SampleValues) : ExampleGroup
NestedExampleGroup.new(@what, parent, build_hooks).tap do |group|
group.children = @children.map do |child|
child.build(group, sample_values).as(ExampleGroup::Child)
child.build(group, sample_values).as(ExampleComponent)
end
end
end

View file

@ -1,13 +1,13 @@
module Spectator::DSL
class RootExampleGroupBuilder < ExampleGroupBuilder
class RootExampleGroupBuilder < NestedExampleGroupBuilder
def initialize
super("ROOT")
end
def build(sample_values : Internals::SampleValues) : ExampleGroup
def build(sample_values : Internals::SampleValues) : RootExampleGroup
RootExampleGroup.new(build_hooks).tap do |group|
group.children = @children.map do |child|
child.build(group, sample_values).as(ExampleGroup::Child)
child.build(group, sample_values).as(ExampleComponent)
end
end
end

View file

@ -1,7 +1,9 @@
require "./example_component"
module Spectator
# Base class for all types of examples.
# Concrete types must implement the `#run_inner` and `#what` methods.
abstract class Example
abstract class Example < ExampleComponent
# Indicates whether the example has already been run.
getter? finished = false
@ -21,9 +23,6 @@ module Spectator
# Implementation-specific for running the example code.
private abstract def run_inner : Result
# Message that describes what the example tests.
abstract def what : String
# Creates the base of the example.
# The group should be the example group the example belongs to.
# The `sample_values` are passed to the example code.

View file

@ -0,0 +1,11 @@
module Spectator
# Abstract base for all examples and collections of examples.
# This is used as the base node type for the composite design pattern.
abstract class ExampleComponent
# Text that describes the context or test.
abstract def what : String
# Indicates whether the example (or group) has been completely run.
abstract def finished? : Bool
end
end

View file

@ -1,89 +1,82 @@
require "./example"
require "./example_component"
module Spectator
class ExampleGroup
alias Child = Example | ExampleGroup
abstract class ExampleGroup < ExampleComponent
include Enumerable(ExampleComponent)
include Iterable(ExampleComponent)
getter what : String
getter! parent : ExampleGroup
private getter! children : Array(Child)
setter children
def initialize(@what, @parent, @hooks : ExampleHooks)
def initialize(@hooks : ExampleHooks)
@before_all_hooks_run = false
@after_all_hooks_run = false
end
def example_count
children.sum do |child|
child.is_a?(Example) ? 1 : child.example_count
private getter! children : Array(ExampleComponent)
def children=(children : Array(ExampleComponent))
raise "Attempted to reset example group children" if @children
@children = children
end
def each
children.each do |child|
yield child
end
end
def each : Iterator(ExampleComponent)
raise NotImplementedError.new("ExampleGroup#each")
end
# TODO: Remove this method.
def example_count
children.sum do |child|
child.is_a?(Example) ? 1 : child.as(ExampleGroup).example_count
end
end
# TODO: Remove this method.
def all_examples
Array(Example).new(example_count).tap do |array|
children.each do |child|
if child.is_a?(Example)
array << child
else
array.concat(child.all_examples)
array.concat(child.as(ExampleGroup).all_examples)
end
end
end
end
def run_before_all_hooks
if (parent = @parent)
parent.run_before_all_hooks
end
def finished? : Bool
children.all?(&.finished?)
end
def run_before_all_hooks : Nil
unless @before_all_hooks_run
@hooks.run_before_all
@before_all_hooks_run = true
end
end
def run_before_each_hooks
if (parent = @parent)
parent.run_before_each_hooks
end
def run_before_each_hooks : Nil
@hooks.run_before_each
end
def run_after_all_hooks
def run_after_all_hooks : Nil
unless @after_all_hooks_run
if all_examples.all?(&.finished?)
if finished?
@hooks.run_after_all
@after_all_hooks_run = true
end
end
if (parent = @parent)
parent.run_after_all_hooks
end
end
def run_after_each_hooks
def run_after_each_hooks : Nil
@hooks.run_after_each
if (parent = @parent)
parent.run_after_each_hooks
end
end
def wrap_around_each_hooks(&block : ->)
wrapper = @hooks.wrap_around_each(&block)
if (parent = @parent)
wrapper = parent.wrap_around_each_hooks(&wrapper)
end
wrapper
end
def to_s(io)
if (parent = @parent)
parent.to_s(io)
io << ' '
end
io << what
def wrap_around_each_hooks(&block : ->) : ->
@hooks.wrap_around_each(&block)
end
end
end

View file

@ -12,12 +12,14 @@ require "./matchers"
require "./formatters"
# Then all of the top-level types.
require "./example_component"
require "./example"
require "./runnable_example"
require "./pending_example"
require "./example_hooks"
require "./example_group"
require "./nested_example_group"
require "./root_example_group"
require "./expectation_failed"

View file

@ -0,0 +1,57 @@
require "./example_group"
module Spectator
class NestedExampleGroup < ExampleGroup
getter what : String
getter! parent : ExampleGroup
def initialize(@what, @parent, hooks : ExampleHooks)
super(hooks)
end
def run_before_all_hooks : Nil
if (parent = @parent)
parent.run_before_all_hooks
end
super
end
def run_before_each_hooks : Nil
if (parent = @parent)
parent.run_before_each_hooks
end
super
end
def run_after_all_hooks : Nil
super
if (parent = @parent)
parent.run_after_all_hooks
end
end
def run_after_each_hooks : Nil
super
if (parent = @parent)
parent.run_after_each_hooks
end
end
def wrap_around_each_hooks(&block : ->) : ->
super(&block).tap do |wrapper|
if (parent = @parent)
wrapper = parent.wrap_around_each_hooks(&wrapper)
end
end
end
def to_s(io)
if (parent = @parent)
parent.to_s(io)
io << ' '
end
io << what
end
end
end

View file

@ -2,8 +2,8 @@ require "./example_group"
module Spectator
class RootExampleGroup < ExampleGroup
def initialize(hooks)
super("ROOT", nil, hooks)
def what : String
"ROOT"
end
def to_s(io)