product: maestro audience: test-developer authority: normative
Design Principle: One YAML Step = One Method/Function
Writing one large method that performs connect, setup, all measurements, and teardown in a single call IS an anti-pattern. It defeats the step-based YAML model.
Why it matters
| Aspect | One method per step ✅ | Everything in one method ❌ |
|---|---|---|
| Retry granularity | Retry only the failed step | Must re-run the entire sequence |
| Skip/disable during dev | Skip any step via enabled: false or precondition |
All or nothing |
| Traceability | Each measurement has its own named step with verdict | One opaque result |
| Abort safety | Teardown steps run via run_on_abort: true |
If the method throws, cleanup may not run |
| Local MSTest coverage | Each method tested independently | Must run the whole sequence |
Anti-pattern
// Don't — one method doing connect, scan, test, and disconnect
public static void RunFullTest(string host, string serialNumber)
{
var client = new InstrumentClient(host);
client.Reset();
// ... scan I2C ...
// ... test interconnect ...
// ... program EEPROM ...
client.Dispose();
}
Correct pattern
// Each method maps to exactly one YAML step
public static void Connect(string host) { ... }
public static void LoadModule() { ... }
public static List<MeasurementPointBase> ScanI2C() { ... }
public static List<MeasurementPointBase> TestInterconnect() { ... }
public static void ProgramDevice(string productId, string revision, string serialNumber) { ... }
public static void Disconnect() { ... }
# Same principle in Python
def connect(host: str) -> None: ...
def load_module() -> None: ...
def scan_i2c() -> dict: ...
def test_interconnect() -> dict: ...
def program_device(product_id: str, revision: str, serial_number: str) -> None: ...
def disconnect() -> None: ...
Rule of thumb for deciding where to split
- If you would give an operation its own row in a test specification, it gets its own method and its own YAML step.
- If an operation can fail independently from the others around it, it gets its own step so its verdict is recorded separately.
- Setup operations (power on, connect, load firmware) ARE always individual steps with
post_execution_action: terminate-on-fail. - Cleanup operations (power off, disconnect) ARE always individual steps with
run_on_abort: true.