5.6 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.
Session Outline
- 5 min connect: Frustrating agent conversations
- 5 min concept: LLMs are pattern matchers
- 35 min concrete practice: Refactor tests using a custom matcher example
- 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 — Refactor Tests with a Custom Matcher
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
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?
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.