Programming foundations: C#

Sefism.

Chapter 08

Testing your code

Unit tests on the logic that matters, test-driven development, test doubles and the edge cases that break most beginner programs.

Unit tests

Also called assertions, pytest, Vitest, JUnit, GoogleTest, xUnit, arrange act assert

What
Small automated tests that call one function or class with known input and assert on the result. A test runner finds and runs them all in seconds.
Why
Tests catch regressions the moment you break something and let you refactor with confidence. Structure each as arrange, act, assert.
C#
using Xunit;

public class PricingTests
{
  [Fact]
  public void AddsTwentyPercent() =>
    Assert.Equal(12m, Pricing.WithTax(10m));
  [Fact]
  public void RejectsNegativeAmounts() =>
    Assert.Throws<ArgumentOutOfRangeException>(
      () => Pricing.WithTax(-1m));
}

Watch out

Testing implementation details (private methods, exact calls) instead of behaviour, so every refactor breaks tests that should still pass.

Test-driven development

Also called TDD, red green refactor, red, green, refactor

What
Writing a failing test first (red), then the least code that makes it pass (green), then cleaning up (refactor), in short cycles.
Why
It forces you to decide what the code should do before how, keeps designs testable and builds a safety net as you go.

Watch out

Writing many tests at once before any code, which loses the short feedback loop that makes TDD useful.

Test doubles

Also called mocks, stubs, fakes, dependency injection

What
Stand-ins for real dependencies in tests: a stub returns fixed answers, a fake is a simple working version (an in-memory store), and a mock checks how it was called.
Why
They keep unit tests fast and repeatable when the real code talks to a network, a clock, a database or email.
Example
Pass a FakeClock fixed at 1 March into is_overdue(invoice, clock) instead of reading the system time, so the test gives the same result every day.

Watch out

Mocking so much that the test only checks your mocks and passes while the real code is broken.

Edge cases

Also called boundary values, corner cases

What
Inputs at or beyond the limits of normal use: empty collections, zero, negative numbers, one item, very large values, missing fields and unexpected text.
Why
Most bugs live at the edges. Listing them before coding shapes both the tests and the implementation.
Example
For an average function: an empty list, one value, all equal values, negative numbers and a list too big to sum in a 32-bit int.

Watch out

Testing only the happy path with the example from the task description.

The test pyramid

Also called integration tests, end-to-end tests, E2E

What
A guideline for balancing test types: many fast unit tests, fewer integration tests that check parts working together, and a few slow end-to-end tests through the whole system.
Why
It keeps the suite fast enough to run on every change while still catching problems where parts connect.

Watch out

Relying mainly on slow, flaky end-to-end tests, so people stop running them.

08 · Testing your code8 / 10