Define the observable contract
A table-driven check keeps a scenario name, input, and independently expected value in one readable row. This suits a rule with several small examples that share the same action and comparison. The delivery policy here charges zero for no units, three for one through five units, and six for larger positive quantities. Negative input violates a separate argument contract. A clear table makes these bands reviewable without scattering nearly identical code blocks across the file, while still exposing the particular boundary each row intends to exercise.
Walk through the real program
The empty row supplies zero and expects zero. The small row supplies one and expects three. The boundary row supplies five and also expects three. The large row supplies six and expects six. Each call is compared with the literal expectation stored in its row, so the original program prints four named PASS lines and passed:4/4. This is a real local calculation and comparison. It does not invoke MSTest or xUnit, discover test attributes, isolate fixtures automatically, or make a failing case stop the console program unless the owner adds an explicit failure policy.
Why the design is testable
The loop handles repeated mechanics, while the table carries examples chosen from the contract. Keep that separation small and unsurprising. An expected value that changes through an accumulating formula can conceal the very arithmetic defect being tested. In a mature framework, parameterized-test features can report each row separately and preserve failure context. This lesson uses the same idea without adding a package dependency to the browser program. The portable form is useful for learning, but its printed labels should not be mistaken for the complete reporting and execution features of a dedicated test host.
A boundary that matters
The negative-input branch is not covered by any current row because it throws instead of returning a fee. It belongs in a separate exception expectation, rather than forcing a numeric table to pretend every possible outcome is a number. Also note that the fee is a band result, not a per-unit multiplication: six units cost six under this particular policy, while seven units also cost six. Read the requirement before choosing expected values. A table can be neatly formatted and still document the wrong pricing rule if its author guesses the business meaning.
Practice: isolate one behavior
First add a named row for four units with an expected fee of three, then a row for seven units with an expected fee of six. Predict six PASS rows and a final passed count of six out of six. Hint: write the fee band beside each input before running. Next deliberately put an incorrect expected value in one row and confirm that the row name identifies the disagreement. Restore the independent contractual value afterward rather than changing the implementation to satisfy a deliberately wrong test.
Practice: challenge another assumption
For a second extension, separate the comparison into a small function returning a structured result with name, expected value, actual value, and success. Hint: keep domain logic in DeliveryFee and reporting in the check result; do not compute the expectation from the returned value. Add an explicit nonzero process exit when any row fails in a standalone console project. Then map the same rows to a real framework's data-driven feature in a separate test project, preserving the contract while changing the runner mechanics.
Review the evidence
Review the example by reading its four rows without the function body, then predict each fee from the stated policy. Confirm that a failure would retain enough input and result information to investigate. Table-driven design is valuable when it removes repetitive mechanics without hiding important distinctions between outcomes. Returned values, exceptions, state changes, and effects may need different observation shapes. Keep each table focused enough that its expected data is obvious, and use a genuine test runner when the project needs discovered cases, standard diagnostics, and reliable automation integration.
- The original table prints four PASS rows and passed four out of four.
- Literal expectations come from the fee bands, not DeliveryFee itself.
- Negative-input exceptions need their own appropriate observation.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
int DeliveryFee(int units)
{
if (units < 0) throw new ArgumentOutOfRangeException(nameof(units));
return units == 0 ? 0 : units <= 5 ? 3 : 6;
}
var cases = new[] { (Name: "empty", Units: 0, Expected: 0), (Name: "small", Units: 1, Expected: 3), (Name: "boundary", Units: 5, Expected: 3), (Name: "large", Units: 6, Expected: 6) };
int passed = 0;
foreach (var item in cases)
{
bool correct = DeliveryFee(item.Units) == item.Expected;
if (correct) passed++;
Console.WriteLine($"{item.Name}:{(correct ? "PASS" : "FAIL")}");
}
Console.WriteLine($"passed:{passed}/{cases.Length}");
Expected output
empty:PASS small:PASS boundary:PASS large:PASS passed:4/4
Write expected literals from the rule, not by repeating the production calculation.