Testing & Reliable Design · Lesson 1

State the observable contract

Describe acceptance and rejection before selecting test cases.

Define the observable contract

A useful test begins with an observable promise, not with a list of lines to execute. The reservation predicate accepts a requested quantity only when it is positive and no greater than the available capacity. Its result is a boolean; this small function does not store a reservation, notify a customer, or reduce capacity. Naming those limits prevents the test from claiming more than the code implements. The available-capacity argument is prepared as three in this lesson, so the exercise can isolate acceptance and rejection without involving a database, clock, or concurrent caller.

Arrange inputs and dependencies, act by running one behavior, and assert against an independent expectation. Choose normal, boundary and invalid input cases.View full size ↗
A test separates setup, one behavior and an independent expectation. Printing a result alone does not compare it with the contract.
Read the diagram

Arrange means choosing the input and explicitly controlled dependencies required for one scenario. Keep unrelated setup out so the test's purpose remains visible.

Act means invoking the behavior being examined. Assert means comparing its observable result or effect against an expectation derived from the contract, rather than calculating that expectation with the implementation being tested.

Normal inputs, values at a boundary and invalid inputs answer different questions. Each scenario needs its own specified outcome; an invalid-input case can pass when the required rejection is observed.

A printed result is an observation. The first lesson deliberately prints outcomes before later lessons add explicit comparisons. This diagram describes test structure; it does not supply a framework, runner, coverage report or complete test suite.

Illustration created with ImageGen for SharpRune.

Walk through the real program

The first call requests two units with three available, satisfying both conditions and printing valid:True. A request of zero fails the positive-quantity rule and prints zero:False. A request of four exceeds the prepared capacity and prints excess:False. Those three results are manually predictable from the stated contract. They do not depend on discovering how a test framework compares values. The program is an ordinary portable console example that makes the observations visible; later lessons add automated case tables, state transitions, and stronger reporting around the same basic principle.

Why the design is testable

Separate the inputs, the action, and the observation even when the example is only one expression. The inputs establish the available capacity and requested amount. Calling CanReserve is the action. Comparing or displaying its returned boolean is the observation. An independently written expected result comes from the requirement, not from evaluating the production expression a second time. This distinction matters when a more complicated algorithm arrives: copying its branches into the test can reproduce the same defect and provide a reassuring but uninformative green result.

A boundary that matters

A function name can suggest a wider workflow than its signature actually promises. A successful CanReserve result is not a completed booking and does not protect against another caller consuming capacity afterward. Likewise, compile-time correctness does not establish a business answer. State whether the function assumes nonnegative available capacity or must validate it, and keep that policy visible when the API grows. For a real reservation service, acceptance, state mutation, persistence, and notification become separate observable responsibilities that need appropriate tests and an explicit transaction or concurrency design.

Practice: isolate one behavior

First change the prepared availability from three to two. Predict that the request for two still succeeds while zero and four still fail. Then request exactly the available quantity and one more than it. Hint: the upper boundary is inclusive, so equality is part of the acceptance rule. Record the expected boolean before editing the expression. A test using only requests well inside the valid range cannot distinguish the correct less-than-or-equal comparison from a strict less-than comparison that rejects the final available unit.

Practice: challenge another assumption

For a second exercise, describe and implement a maximum of two units per request even when more stock exists. Add cases that distinguish that new limit from available capacity, such as requesting three with ten available. Hint: write the changed promise first and keep the original stock rule separate from the new per-request rule. Explain which previous cases remain valid and which new cases become necessary. Do not alter expected values merely because the implementation currently returns them; use a disagreement to revisit the intended contract.

Review the evidence

A complete explanation of this sample states its domain assumption, three inputs, three returned results, and the absence of side effects. That is stronger than saying the method works because it ran. In a larger test suite, use names that identify the scenario and expected behavior, and keep unrelated infrastructure outside a pure-rule check. The confidence comes from a clear contract challenged by relevant examples. Neither a console label nor a large coverage percentage can compensate for expectations that were never derived independently from the application's requirement.

  • The baseline prints one true acceptance and two false rejections.
  • CanReserve is a predicate, not a completed storage transaction.
  • Write the expected outcome before inspecting the implementation result.

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;

bool CanReserve(int requested, int available) => requested > 0 && requested <= available;
Console.WriteLine($"valid:{CanReserve(2, 3)}");
Console.WriteLine($"zero:{CanReserve(0, 3)}");
Console.WriteLine($"excess:{CanReserve(4, 3)}");

Expected output

valid:True
zero:False
excess:False
Try this
State inputs, outputs and rejected cases independently of the implementation.

Further reading