- Add OrderBookDomain: in-memory order book with Pending → WaitingForPayment → Fulfilling → Delivered state machine, payment window based on passed-in timestamps, cancellation rules - Add OrderBookTests: 7 integration tests in given/when/then/when/then anti-pattern style as starting material for the exercise - Learning hour: add round 2 where participants create the example themselves before showing it to the agent (no refactored test given) - Fix sln: OrderTests Debug config was mapped to Release
8.7 KiB
theme, title, name, difficulty, author, affiliation, tags
| theme | title | name | difficulty | author | affiliation | tags |
|---|---|---|---|---|---|---|
| agentic_engineering | Show, Don't Tell — Using Examples with Coding Agents | show_dont_tell | 2 | willem | agentic copilot refactoring tests examples |
Show, Don't Tell — Using Examples with Coding Agents
You've described what you want to a coding agent, and it didn't get it right. Or it took so many rounds of corrections that you'd have just done it yourself. This learning hour explores a simpler approach: instead of telling the agent what style you want, show it.
Learning Goals
- Use a concrete code example (show, don't tell) to guide a coding agent in propagating a refactoring pattern across a test suite.
- Produce your own example before delegating: refactor a multi-step test into focused tests yourself, then show the result to the agent as the pattern.
Session Outline
- 5 min connect: Frustrating agent conversations
- 5 min concept: LLMs are pattern matchers
- 20 min concrete practice: Round 1 — refactor tests using a custom matcher example
- 15 min concrete practice: Round 2 — split multi-step tests using an example you create
- 5 min conclusion: Share back and recognize the pattern in your own code
Connect — Frustrating Agent Conversations
Pair up and share a time when you had to iterate multiple times with a coding agent just to get it to match the style you wanted. What did you try? How many rounds did it take?
This is a [Pair Share]({% link _activities/connect/pair_share.md %}) connect.
Concept — LLMs Are Pattern Matchers
LLMs are pattern matchers and translation machines. They are better at recognizing a pattern in concrete code than interpreting abstract instructions.
The "Tell" Approach (doesn't work well)
Refactor these tests to encapsulate the total price check. Instead of asserting on
order.Total, create a methodIsFullyPaid()and test for that.
The agent might:
- Create the method but not use it consistently
- Misinterpret "encapsulate" and change production code instead of test code
- Apply the pattern incorrectly or only partially
The "Show" Approach (works better)
Refactor one test yourself — encapsulate the attribute check behind a behavior method:
// Before — checking an attribute
[Test]
public void CompletedOrder_TotalIsCorrect()
{
var order = new Order();
order.AddLine(100m, 1);
order.Complete();
Assert.That(order.Total, Is.EqualTo(100m));
}
// After — encapsulating behavior
[Test]
public void CompletedOrder_TotalIsCorrect()
{
var order = new Order();
order.AddLine(100m, 1);
order.Complete();
Assert.That(order.IsFullyPaid(100m), Is.True);
}
Then ask the agent:
The first test was refactored to use
order.IsFullyPaid(expected)instead of asserting onorder.Total. Apply the same pattern to the remaining tests.
The agent sees the concrete pattern and replicates it. No description needed.
Key Insight
Examples beat descriptions. One concrete example is worth a paragraph of instructions. This works for any refactoring pattern — splitting tests, introducing custom matchers, extracting methods, renaming for clarity. Show the agent what "done" looks like.
Concrete Practice — Round 1 — Refactor Tests with a Custom Matcher (20 min)
Setup
Open the project in exercises/show-dont-tell/ in Rider with GitHub Copilot Agent mode.
The project contains an Order domain and a test file OrderTotalTests.cs with 6 tests.
Step 1 — Identify the Smell (5 min)
Open OrderTotalTests.cs. Look at the tests. What's the pattern you see?
- The first test (
DiscountOnFirstLine_TotalAndLineValues) was already refactored — notice how it usesHas.OrderState()with a single assertion. - The remaining 5 tests each have multiple
Assert.That()calls checkingorder.Total,order.Lines[0].Value,order.Lines[1].Value, etc.
These multiple asserts are checking attributes (state) rather than expressing intention. They're also hard to read and fragile — if the order structure changes, every test needs updating.
Step 2 — Examine the Example (5 min)
Look at the first test and the custom matcher file OrderStateConstraint.cs. You don't need to understand how the matcher is implemented — just recognize the pattern:
// One assertion that bundles all checks
Assert.That(order, Has.OrderState(
expectedTotal: 210m,
expectedLineValues: new[] { 160m, 50m }));
vs.
// Three separate assertions
Assert.That(order.Total, Is.EqualTo(240m));
Assert.That(order.Lines[0].Value, Is.EqualTo(200m));
Assert.That(order.Lines[1].Value, Is.EqualTo(40m));
Step 3 — Use the Agent (20-25 min)
Use GitHub Copilot Agent mode to refactor the remaining 5 tests. Prompt:
The first test uses a custom matcher
Has.OrderState(). Refactor the remaining tests to use the same pattern — one assertion withHas.OrderState()instead of multiple individual asserts.
Run the tests after the agent makes changes to verify nothing broke.
If the agent doesn't get it right on the first try:
- Check that the example test is visible in the same file
- Be more specific: "Look at
DiscountOnFirstLine_TotalAndLineValuesas the example" - Point out what went wrong and ask it to retry
Tools Needed
- Rider with GitHub Copilot Agent mode
- The
exercises/show-dont-tell/project (included in this repo) - .NET 10 SDK
Concrete Practice — Round 2 — Split Multi-Step Tests (15 min)
This round has a twist: no refactored example is provided. Creating the example is the exercise.
Setup
In the same solution, open the OrderBookTests project. The domain is an OrderBook with a small state machine:
OpenOrder(customer)creates aPendingorderFinalizeOrder(id, at)→WaitingForPayment(the timestamp is passed in — no global clock)PayOrder(id, paidAt)→Fulfilling, unless payment arrives more than 14 days after finalization: the order isCancelledand the payment is rejectedShipOrder(id)→DeliveredCancelOrder(id)works fromPendingandWaitingForPaymentonly; invalid transitions throw
OrderBookLifecycleTests.cs contains 7 integration tests in an anti-pattern style: given/when/then/when/then. Each test walks through several transitions with assertions in between, e.g. PayThenShip_DeliversTheOrder finalizes, asserts, pays, ships, and asserts again. The tests pass — they're just testing three behaviors each under a name that describes one.
Step 1 — Identify the Smell (3 min)
Look at the tests. What makes them hard to work with?
- Multiple act/assert pairs: when one assertion fails, you don't know which transition misbehaved
- Test names describe one behavior while the test body verifies three
- Setup for a later transition is entangled with assertions for an earlier one
Step 2 — Create the Example Yourself (7 min)
Pick one multi-step test and refactor it by hand into focused tests: one transition per test, each test setting up only the state it needs. Keep the exception paths (Assert.Throws) in their own tests. Run the tests — the split versions must cover the same behavior as the original.
You cannot show the agent what "done" looks like until you have built it. Notice what decisions you had to make: how to name the split tests, what setup helper to extract, where a transition deserves its own test.
Step 3 — Use Your Example with the Agent (5 min)
Now show your refactored test to the agent and ask it to apply the same pattern to the remaining tests. Verify with the test run afterward.
If the agent doesn't get it right:
- Check whether your example is unambiguous — could it be read another way?
- Compare with round 1: what did the provided example communicate that yours doesn't?
Why No Example Was Provided
Round 1 showed that examples guide agents better than descriptions. Round 2 practices the harder half of that skill: producing the example. In real work, the pattern you want to propagate usually doesn't exist yet — someone has to write it first, and that someone is you.
Conclusions — Share Back
Go around and ask:
- Did the agent get it right on the first try? What happened if it didn't?
- What made the example work — what would have made it fail?
- Do you recognize this pattern in your own tests? Where could you use "show, don't tell" in your real work?
- What was different in round 2, when you had to create the example yourself? What made that harder — and what did you learn about writing a good example?
This is an [Explain the Main Idea]({% link _activities/conclusions/explain_main_idea.md %}) conclusion.
One-Sentence Takeaway
When working with coding agents, show them what you want with a concrete example instead of describing it in words — LLMs are pattern matchers, and examples are patterns.