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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,9 @@
require "./example_component"
module Spectator module Spectator
# Base class for all types of examples. # Base class for all types of examples.
# Concrete types must implement the `#run_inner` and `#what` methods. # 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. # Indicates whether the example has already been run.
getter? finished = false getter? finished = false
@ -21,9 +23,6 @@ module Spectator
# Implementation-specific for running the example code. # Implementation-specific for running the example code.
private abstract def run_inner : Result private abstract def run_inner : Result
# Message that describes what the example tests.
abstract def what : String
# Creates the base of the example. # Creates the base of the example.
# The group should be the example group the example belongs to. # The group should be the example group the example belongs to.
# The `sample_values` are passed to the example code. # 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 module Spectator
class ExampleGroup abstract class ExampleGroup < ExampleComponent
alias Child = Example | ExampleGroup include Enumerable(ExampleComponent)
include Iterable(ExampleComponent)
getter what : String def initialize(@hooks : ExampleHooks)
getter! parent : ExampleGroup
private getter! children : Array(Child)
setter children
def initialize(@what, @parent, @hooks : ExampleHooks)
@before_all_hooks_run = false @before_all_hooks_run = false
@after_all_hooks_run = false @after_all_hooks_run = false
end end
def example_count private getter! children : Array(ExampleComponent)
children.sum do |child|
child.is_a?(Example) ? 1 : child.example_count 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
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 def all_examples
Array(Example).new(example_count).tap do |array| Array(Example).new(example_count).tap do |array|
children.each do |child| children.each do |child|
if child.is_a?(Example) if child.is_a?(Example)
array << child array << child
else else
array.concat(child.all_examples) array.concat(child.as(ExampleGroup).all_examples)
end end
end end
end end
end end
def run_before_all_hooks def finished? : Bool
if (parent = @parent) children.all?(&.finished?)
parent.run_before_all_hooks
end end
def run_before_all_hooks : Nil
unless @before_all_hooks_run unless @before_all_hooks_run
@hooks.run_before_all @hooks.run_before_all
@before_all_hooks_run = true @before_all_hooks_run = true
end end
end end
def run_before_each_hooks def run_before_each_hooks : Nil
if (parent = @parent)
parent.run_before_each_hooks
end
@hooks.run_before_each @hooks.run_before_each
end end
def run_after_all_hooks def run_after_all_hooks : Nil
unless @after_all_hooks_run unless @after_all_hooks_run
if all_examples.all?(&.finished?) if finished?
@hooks.run_after_all @hooks.run_after_all
@after_all_hooks_run = true @after_all_hooks_run = true
end end
end end
if (parent = @parent)
parent.run_after_all_hooks
end
end end
def run_after_each_hooks def run_after_each_hooks : Nil
@hooks.run_after_each @hooks.run_after_each
if (parent = @parent)
parent.run_after_each_hooks
end
end end
def wrap_around_each_hooks(&block : ->) def wrap_around_each_hooks(&block : ->) : ->
wrapper = @hooks.wrap_around_each(&block) @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
end end
end end
end end

View file

@ -12,12 +12,14 @@ require "./matchers"
require "./formatters" require "./formatters"
# Then all of the top-level types. # Then all of the top-level types.
require "./example_component"
require "./example" require "./example"
require "./runnable_example" require "./runnable_example"
require "./pending_example" require "./pending_example"
require "./example_hooks" require "./example_hooks"
require "./example_group" require "./example_group"
require "./nested_example_group"
require "./root_example_group" require "./root_example_group"
require "./expectation_failed" 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 module Spectator
class RootExampleGroup < ExampleGroup class RootExampleGroup < ExampleGroup
def initialize(hooks) def what : String
super("ROOT", nil, hooks) "ROOT"
end end
def to_s(io) def to_s(io)