Patterns

Value Differentiation

When filling in mock or dummy data, make each field value as distinct from the others as possible. For example, set all integer values to different integers.

Benefits:
- easier for a human to visually scan the data
- more likely to reveal errors such as mapping the wrong field

Common Initialization

When writing a test suite, write a method that constructs a new, full, valid object.
Let each test use this method to create its own instantiation of the object, and make just the edits needed to setup this test case.

Scenario Builder

Create a ScenarioBuilder class for the test suite of one class.

The ScenarioBuilder will contain methods for setting up the test cases and verifying the results. This makes it easy to put a business-legible label (the method name) on each unit of setup and verification code. This is especially useful when using mocks, which require repetitive setup and verification calls.

Each test will instantiate its own ScenarioBuilder to keep the tests isolated from each other.

Write the setup methods so you can use Method Chaining (i.e. have each setup method return the ScenarioBuilder itself). This makes the test cases easy to read.
Ex:

public void Test_UseCase()
{
    var customerResponse = ...
    var orderResponse = ...
    var databaseResponse = ...
    var scenarioBuilder = 
        new ScenarioBuilder()
        .WithCustomer(customerResponse)
        .WithOrder(orderResponse)
        .WithDBUpdate(databaseResponse)
        
    var result = scenarioBuilder.RunUseCase();
    
    scenarioBuilder.VerifyResult(result);
}

Test-per-Case Vs Test-per-Setup

A consideration for slower tests.

Test-per-Case: One test per use case. This isolates the errors.

Test-per-Setup: One test per setup (or initialize), then performs several different use case tests. This saves time when the setup is slow.
Test Driven Development

(These notes are based on minimal research, so far.)

Methodology

1) Write a failing test case, for the smallest unit of functionality you are working on.
2) Write just enough code (new feature, or bug fix) to make that test case pass.
3) Repeat.

You can refactor anytime all your test cases are passing.

"Write just enough code" can mean hard-coding results first, as you work out your object structure. And only then return actual calculations.

Reasons

You code will be written to be testable.

You'll build a comprehensive automated test suite.

TTD can be a good design tool, when you haven't written a feature yet. It can be used as a whiteboard, that will actually compile when you've cleaned it up.