Graph to Specs
Graph to Specs: the plan's structure dictates its test plan - roots need fixture cases, joins need one case per missing tributary, leaves need output assertions. This generates the RSpec skeleton from the graph, so "what should we test?" stops being a staring contest with a blank file.
Testing & Verification
Round 8
Sandi Metz
exit 0
bundle exec ruby examples/graph_to_specs.rb
a real captured run
# generated from the plan's graph - one describe per task,
# examples dictated by each task's structural role
RSpec.describe "the pipeline" do
describe "fetch orders" do # root
it "produces output from fixture input" # roots own the boundary with the world
it "raises a named error when the source is unreachable"
end
describe "fetch refunds" do # root
it "produces output from fixture input" # roots own the boundary with the world
it "raises a named error when the source is unreachable"
end
describe "build ledger" do # join
context "with all 2 inputs present" do
it "combines sales and credits"
end
context "when sales is missing" do # joins fail per-tributary, not vaguely
it "reports which input was absent"
end
context "when credits is missing" do # joins fail per-tributary, not vaguely
it "reports which input was absent"
end
end
describe "render report" do # leaf
it "transforms its upstream's output" # assert on previous_output's shape
it "produces the artifact consumers read" # leaves are promises to the outside
end
end
# 4 tasks -> 2 boundary suites, 1 join suites with per-tributary absence cases, 1 artifact suites.
# the graph decided what deserves a test; you decide what passes one.
source
# frozen_string_literal: true # Graph to Specs: the plan's structure dictates its test plan - roots # need fixture cases, joins need one case per missing tributary, # leaves need output assertions. This generates the RSpec skeleton # from the graph, so "what should we test?" stops being a staring # contest with a blank file. # # bundle exec ruby examples/graph_to_specs.rb # # Runs offline; prints a runnable-shaped spec skeleton. require class="s">"bundler/setup" require class="s">"agentic" def step(name) Agentic:class="y">:Task.new(description: name, agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"work"}) end orchestrator = Agentic:class="y">:PlanOrchestrator.new orders = step(class="s">"fetch orders") refunds = step(class="s">"fetch refunds") ledger = step(class="s">"build ledger") report = step(class="s">"render report") orchestrator.add_task(orders) orchestrator.add_task(refunds) orchestrator.add_task(ledger, needs: {sales: orders, credits: refunds}) orchestrator.add_task(report, [ledger]) graph = orchestrator.graph stats = graph[class="y">:stats] names = graph[class="y">:tasks].transform_values(&class="y">:description) puts class="s">"# generated from the plan's graph - one describe per task," puts class="s">"# examples dictated by each task's structural role" puts puts class="s">"RSpec.describe \"the pipeline\class="s">" do" graph[class="y">:order].each do |id| name = names[id] deps = graph[class="y">:dependencies][id] labeled = graph[class="y">:edges].select { |e| e[class="y">:to] == id && e[class="y">:label] } role = [] role << class="s">"root" if stats[class="y">:roots].include?(id) role << class="s">"join" if deps.size >= 2 role << class="s">"leaf" if stats[class="y">:leaves].include?(id) puts class="s">" describe \"#{name}\class="s">" do # #{role.join(", class="s">")}" if stats[class="y">:roots].include?(id) puts class="s">" it \"produces output from fixture input\class="s">" # roots own the boundary with the world" puts class="s">" it \"raises a named error when the source is unreachable\class="s">"" end if deps.size >= 2 puts class="s">" context \"with all #{deps.size} inputs present\class="s">" do" puts class="s">" it \"combines #{labeled.map { |e| e[class="y">:label] }.join(class="s">" and ")}\class="s">"" puts class="s">" end" labeled.each do |edge| puts class="s">" context \"when #{edge[class="y">:label]} is missing\class="s">" do # joins fail per-tributary, not vaguely" puts class="s">" it \"reports which input was absent\class="s">"" puts class="s">" end" end elsif deps.size == 1 puts class="s">" it \"transforms its upstream's output\class="s">" # assert on previous_output's shape" end if stats[class="y">:leaves].include?(id) puts class="s">" it \"produces the artifact consumers read\class="s">" # leaves are promises to the outside" end puts class="s">" end" puts end puts class="s">"end" puts puts class="s">"# #{graph[class="y">:tasks].size} tasks -> #{stats[class="y">:roots].size} boundary suites, " \ class="s">"#{graph[class="y">:dependencies].count { |_, d| d.size >= 2 }} join suites with " \ class="s">"per-tributary absence cases, #{stats[class="y">:leaves].size} artifact suites." puts class="s">"# the graph decided what deserves a test; you decide what passes one."