Compare commits
6
Commits
5965ed8998
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06a91a582e | ||
|
|
e8748ee697 | ||
|
|
3b7e880717 | ||
|
|
4c0f1dd541 | ||
|
|
c6c5829748 | ||
|
|
1736cd1abd |
@@ -17,3 +17,4 @@ obj/
|
||||
|
||||
## NuGet local config (workaround for locked ~/.nuget)
|
||||
nuget.config
|
||||
*.log
|
||||
|
||||
@@ -6,6 +6,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderDomain", "src/OrderDom
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTests", "tests/OrderTests/OrderTests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderBookDomain", "src/OrderBookDomain/OrderBookDomain.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderBookTests", "tests/OrderBookTests/OrderBookTests.csproj", "{D4E5F6A7-B8C9-0123-DEFA-234567890123}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{83A6B86A-4CD0-44BE-9FE0-408AC31E69F8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Warmup", "Warmup", "{55470487-3CE4-4FD7-BADF-35F7D1473CA5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -14,4 +22,24 @@ Global
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-234567890123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-234567890123}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D4E5F6A7-B8C9-0123-DEFA-234567890123}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{C3D4E5F6-A7B8-9012-CDEF-123456789012} = {83A6B86A-4CD0-44BE-9FE0-408AC31E69F8}
|
||||
{D4E5F6A7-B8C9-0123-DEFA-234567890123} = {83A6B86A-4CD0-44BE-9FE0-408AC31E69F8}
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {55470487-3CE4-4FD7-BADF-35F7D1473CA5}
|
||||
{B2C3D4E5-F6A7-8901-BCDE-F12345678901} = {55470487-3CE4-4FD7-BADF-35F7D1473CA5}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.400",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,14 @@ You've described what you want to a coding agent, and it didn't get it right. Or
|
||||
## 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
|
||||
* 35 min concrete practice: Refactor tests using a custom matcher example
|
||||
* 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
|
||||
@@ -80,7 +82,7 @@ The agent sees the concrete pattern and replicates it. No description needed.
|
||||
|
||||
**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
|
||||
## Concrete Practice — Round 1 — Refactor Tests with a Custom Matcher (20 min)
|
||||
|
||||
### Setup
|
||||
|
||||
@@ -136,6 +138,49 @@ Run the tests after the agent makes changes to verify nothing broke.
|
||||
- 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 a `Pending` order
|
||||
* `FinalizeOrder(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 is `Cancelled` and the payment is rejected
|
||||
* `ShipOrder(id)` → `Delivered`
|
||||
* `CancelOrder(id)` works from `Pending` and `WaitingForPayment` only; 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:
|
||||
@@ -143,6 +188,7 @@ 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?
|
||||
4. **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.
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[tools]
|
||||
dotnet = "10"
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace OrderBookDomain;
|
||||
|
||||
public enum OrderStatus
|
||||
{
|
||||
Pending,
|
||||
WaitingForPayment,
|
||||
Fulfilling,
|
||||
Delivered,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
public class Order
|
||||
{
|
||||
public int Id { get; }
|
||||
public string Customer { get; }
|
||||
public OrderStatus Status { get; internal set; }
|
||||
public DateTimeOffset? FinalizedAt { get; internal set; }
|
||||
|
||||
internal Order(int id, string customer)
|
||||
{
|
||||
Id = id;
|
||||
Customer = customer;
|
||||
Status = OrderStatus.Pending;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
namespace OrderBookDomain;
|
||||
|
||||
public class OrderBook
|
||||
{
|
||||
private readonly TimeSpan _paymentWindow;
|
||||
private readonly Dictionary<int, Order> _ordersById = new();
|
||||
private int _nextId = 1;
|
||||
|
||||
public OrderBook(TimeSpan? paymentWindow = null)
|
||||
=> _paymentWindow = paymentWindow ?? TimeSpan.FromDays(14);
|
||||
|
||||
public IReadOnlyList<Order> Orders => _ordersById.Values.ToList();
|
||||
|
||||
public Order OpenOrder(string customer)
|
||||
{
|
||||
var order = new Order(_nextId++, customer);
|
||||
_ordersById[order.Id] = order;
|
||||
return order;
|
||||
}
|
||||
|
||||
public Order GetOrder(int orderId)
|
||||
=> _ordersById.TryGetValue(orderId, out var order)
|
||||
? order
|
||||
: throw new InvalidOperationException($"Unknown order id {orderId}.");
|
||||
|
||||
public void FinalizeOrder(int orderId, DateTimeOffset finalizedAt)
|
||||
{
|
||||
var order = GetOrder(orderId);
|
||||
RequireStatus(order, OrderStatus.Pending, "finalize");
|
||||
order.Status = OrderStatus.WaitingForPayment;
|
||||
order.FinalizedAt = finalizedAt;
|
||||
}
|
||||
|
||||
public void PayOrder(int orderId, DateTimeOffset paidAt)
|
||||
{
|
||||
var order = GetOrder(orderId);
|
||||
RequireStatus(order, OrderStatus.WaitingForPayment, "pay");
|
||||
if (paidAt - order.FinalizedAt > _paymentWindow)
|
||||
{
|
||||
order.Status = OrderStatus.Cancelled;
|
||||
throw new InvalidOperationException(
|
||||
$"Payment for order {orderId} arrived after the payment window; the order was cancelled.");
|
||||
}
|
||||
order.Status = OrderStatus.Fulfilling;
|
||||
}
|
||||
|
||||
public void ShipOrder(int orderId)
|
||||
{
|
||||
var order = GetOrder(orderId);
|
||||
RequireStatus(order, OrderStatus.Fulfilling, "ship");
|
||||
order.Status = OrderStatus.Delivered;
|
||||
}
|
||||
|
||||
public void CancelOrder(int orderId)
|
||||
{
|
||||
var order = GetOrder(orderId);
|
||||
if (order.Status is not (OrderStatus.Pending or OrderStatus.WaitingForPayment))
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot cancel order {orderId} in status {order.Status}.");
|
||||
order.Status = OrderStatus.Cancelled;
|
||||
}
|
||||
|
||||
private static void RequireStatus(Order order, OrderStatus expected, string action)
|
||||
{
|
||||
if (order.Status != expected)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot {action} order {order.Id} in status {order.Status} (expected {expected}).");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,114 @@
|
||||
using NUnit.Framework;
|
||||
using OrderBookDomain;
|
||||
|
||||
namespace OrderBookTests;
|
||||
|
||||
public class OrderBookLifecycleTests
|
||||
{
|
||||
private static readonly DateTimeOffset Day0 = new(2025, 6, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private static DateTimeOffset At(int day) => Day0.AddDays(day);
|
||||
|
||||
[Test]
|
||||
public void FinalizeThenPay_MovesOrderThroughTwoStates()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var order = book.OpenOrder("anna");
|
||||
|
||||
book.FinalizeOrder(order.Id, At(1));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||
|
||||
book.PayOrder(order.Id, At(2));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Fulfilling));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PayThenShip_DeliversTheOrder()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var order = book.OpenOrder("anna");
|
||||
|
||||
book.FinalizeOrder(order.Id, At(1));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||
|
||||
book.PayOrder(order.Id, At(2));
|
||||
book.ShipOrder(order.Id);
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Delivered));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PayWithinWindow_ThenLatePayOnSecondOrder_CancelsOnlyThatOrder()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var first = book.OpenOrder("anna");
|
||||
var second = book.OpenOrder("bram");
|
||||
|
||||
book.FinalizeOrder(first.Id, At(1));
|
||||
book.FinalizeOrder(second.Id, At(1));
|
||||
Assert.That(book.Orders, Has.Count.EqualTo(2));
|
||||
|
||||
book.PayOrder(first.Id, At(5));
|
||||
Assert.That(book.GetOrder(first.Id).Status, Is.EqualTo(OrderStatus.Fulfilling));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => book.PayOrder(second.Id, At(30)));
|
||||
Assert.That(book.GetOrder(second.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PayBeforeFinalize_Throws_ThenFinalizeStillWorks()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var order = book.OpenOrder("anna");
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => book.PayOrder(order.Id, At(1)));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Pending));
|
||||
|
||||
book.FinalizeOrder(order.Id, At(2));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CancelPendingOrder_ThenPayAfterCancel_ThrowsAgain()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var order = book.OpenOrder("anna");
|
||||
|
||||
book.CancelOrder(order.Id);
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => book.PayOrder(order.Id, At(1)));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShippedOrder_CannotBeCancelled()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var order = book.OpenOrder("anna");
|
||||
|
||||
book.FinalizeOrder(order.Id, At(1));
|
||||
book.PayOrder(order.Id, At(2));
|
||||
book.ShipOrder(order.Id);
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Delivered));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => book.CancelOrder(order.Id));
|
||||
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Delivered));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TwoCustomers_InterleaveTransitions_Independently()
|
||||
{
|
||||
var book = new OrderBook();
|
||||
var annas = book.OpenOrder("anna");
|
||||
var brams = book.OpenOrder("bram");
|
||||
|
||||
book.FinalizeOrder(brams.Id, At(1));
|
||||
Assert.That(book.GetOrder(annas.Id).Status, Is.EqualTo(OrderStatus.Pending));
|
||||
Assert.That(book.GetOrder(brams.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||
|
||||
book.PayOrder(brams.Id, At(2));
|
||||
book.CancelOrder(annas.Id);
|
||||
Assert.That(book.GetOrder(annas.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||
Assert.That(book.GetOrder(brams.Id).Status, Is.EqualTo(OrderStatus.Fulfilling));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NUnit" Version="4.6.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OrderBookDomain\OrderBookDomain.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -12,9 +12,7 @@ namespace OrderTests;
|
||||
public static class Has
|
||||
{
|
||||
public static IResolveConstraint OrderState(decimal expectedTotal, decimal[] expectedLineValues)
|
||||
{
|
||||
return new OrderStateConstraint(expectedTotal, expectedLineValues);
|
||||
}
|
||||
=> new OrderStateConstraint(expectedTotal, expectedLineValues);
|
||||
}
|
||||
|
||||
public class OrderStateConstraint : Constraint
|
||||
@@ -23,27 +21,54 @@ public class OrderStateConstraint : Constraint
|
||||
private readonly decimal[] _expectedLineValues;
|
||||
|
||||
public OrderStateConstraint(decimal expectedTotal, decimal[] expectedLineValues)
|
||||
: base($"order with total {expectedTotal} and line values [{string.Join(", ", expectedLineValues)}]")
|
||||
{
|
||||
_expectedTotal = expectedTotal;
|
||||
_expectedLineValues = expectedLineValues;
|
||||
}
|
||||
|
||||
public override string Description =>
|
||||
$"order with total {_expectedTotal} and line values [{string.Join(", ", _expectedLineValues)}]";
|
||||
|
||||
public override ConstraintResult ApplyTo<TActual>(TActual actual)
|
||||
{
|
||||
var order = (Order)(object)actual!;
|
||||
var actualTotal = order.Total;
|
||||
var actualLineValues = order.Lines.Select(l => l.Value).ToArray();
|
||||
|
||||
var totalMatch = actualTotal == _expectedTotal;
|
||||
var linesMatch = actualLineValues.SequenceEqual(_expectedLineValues);
|
||||
var details = new List<string>();
|
||||
if (actualTotal != _expectedTotal)
|
||||
details.Add($"Total: expected {_expectedTotal}, but was {actualTotal}");
|
||||
if (!actualLineValues.SequenceEqual(_expectedLineValues))
|
||||
details.Add(
|
||||
$"Line values: expected [{string.Join(", ", _expectedLineValues)}], " +
|
||||
$"but was [{string.Join(", ", actualLineValues)}]");
|
||||
|
||||
var messages = new List<string>();
|
||||
if (!totalMatch)
|
||||
messages.Add($"Total: expected {_expectedTotal}, got {actualTotal}");
|
||||
if (!linesMatch)
|
||||
messages.Add($"Line values: expected [{string.Join(", ", _expectedLineValues)}], got [{string.Join(", ", actualLineValues)}]");
|
||||
|
||||
return new ConstraintResult(this, actual, totalMatch && linesMatch);
|
||||
return new OrderStateResult(this, actual!, details.Count == 0, details);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A ConstraintResult that carries the per-field mismatch details and
|
||||
/// writes them as additional lines in the failure message, e.g.
|
||||
///
|
||||
/// Expected: order with total 210 and line values [160, 50]
|
||||
/// But was: <Order>
|
||||
/// Total: expected 210, but was 250
|
||||
/// Line values: expected [160, 50], but was [200, 50]
|
||||
/// </summary>
|
||||
private sealed class OrderStateResult : ConstraintResult
|
||||
{
|
||||
private readonly IReadOnlyList<string> _details;
|
||||
|
||||
public OrderStateResult(Constraint constraint, object? actual, bool isSuccess, IReadOnlyList<string> details)
|
||||
: base(constraint, actual, isSuccess)
|
||||
{
|
||||
_details = details;
|
||||
}
|
||||
|
||||
public override void WriteAdditionalLinesTo(MessageWriter writer)
|
||||
{
|
||||
foreach (var detail in _details)
|
||||
writer.WriteMessageLine(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.6.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\OrderDomain\OrderDomain.csproj" />
|
||||
|
||||
@@ -18,7 +18,7 @@ public class OrderTotalTests
|
||||
|
||||
Assert.That(order, Has.OrderState(
|
||||
expectedTotal: 210m,
|
||||
expectedLineValues: new[] { 160m, 50m }));
|
||||
expectedLineValues: [160m, 50m]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
Reference in New Issue
Block a user