Files
show-dont-tell/learning-hour.md
T
2026-09-03 08:33:54 +01:00

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 method IsFullyPaid() 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 on order.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 uses Has.OrderState() with a single assertion.
  • The remaining 5 tests each have multiple Assert.That() calls checking order.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 with Has.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_TotalAndLineValues as 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:

  1. Did the agent get it right on the first try? What happened if it didn't?
  2. What made the example work — what would have made it fail?
  3. 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.