Files
weevil/templates/testing/TESTING_SHOWCASE.md
Eric Ratliff 60679e097f feat: Add template system to weevil new command
Implements template-based project creation allowing teams to start with
professional example code instead of empty projects.

Features:
- Two templates: 'basic' (minimal) and 'testing' (45-test showcase)
- Template variable substitution ({{PROJECT_NAME}}, etc.)
- Template validation with helpful error messages
- `weevil new --list-templates` command
- Template files embedded in binary at compile time

Technical details:
- Templates stored in templates/basic/ and templates/testing/
- Files ending in .template have variables replaced
- Uses include_dir! macro to embed templates in binary
- Returns file count for user feedback

Testing template includes:
- 3 complete subsystems (MotorCycler, WallApproach, TurnController)
- Hardware abstraction layer with mock implementations
- 45 comprehensive tests (unit, integration, system)
- Professional documentation (DESIGN_AND_TEST_PLAN.md, etc.)

Usage:
  weevil new my-robot                    # basic template
  weevil new my-robot --template testing # testing showcase
  weevil new --list-templates            # show available templates

This enables FTC teams to learn from working code and best practices
rather than starting from scratch.
2026-02-02 22:31:08 -06:00

411 lines
8.9 KiB
Markdown

# Testing Showcase: Professional Robotics Without Hardware
This project demonstrates **industry-standard testing practices** for robotics code.
## The Revolutionary Idea
**You can test robot logic without a robot!**
Traditional FTC:
- Write code
- Deploy to robot (5+ minutes)
- Test on robot
- Find bug
- Repeat...
With proper testing:
- Write code
- Run tests (2 seconds)
- Fix bugs instantly
- Deploy confident code to robot
## Test Categories in This Project
### 1. Unit Tests (Component-Level)
Test individual behaviors in isolation.
**Example: Motor Power Levels**
```java
@Test
void testFullSpeedWhenFar() {
sensor.setDistance(100.0); // Far from wall
wallApproach.start();
wallApproach.update();
assertEquals(0.6, motor.getPower(), 0.001, // Full speed
"Should drive at full speed when far");
}
```
**What this tests:**
- Speed control logic
- Distance threshold detection
- Motor power calculation
**Time to run:** ~5 milliseconds
### 2. System Tests (Complete Scenarios)
Test entire sequences working together.
**Example: Complete Autonomous Mission**
```java
@Test
void testCompleteAutonomousMission() {
// Phase 1: Drive 100cm to wall
distanceSensor.setDistance(100.0);
wallApproach.start();
while (wallApproach.getState() != STOPPED) {
wallApproach.update();
distanceSensor.approach(motor.getPower() * 2.0);
}
// Phase 2: Turn 90° right
turnController.turnTo(90);
while (turnController.getState() == TURNING) {
turnController.update();
gyro.rotate(motor.getPower() * 2.0);
}
// Verify complete mission success
assertEquals(STOPPED, wallApproach.getState());
assertEquals(90, gyro.getHeading(), 2.0);
}
```
**What this tests:**
- Multiple subsystems coordinating
- State transitions between phases
- Sensor data flowing correctly
- Complete mission execution
**Time to run:** ~50 milliseconds
### 3. Edge Case Tests (Failure Modes)
Test things that are hard/dangerous to test on a real robot.
**Example: Sensor Failure**
```java
@Test
void testSensorFailureHandling() {
wallApproach.start();
// Sensor suddenly disconnects!
sensor.simulateFailure();
wallApproach.update();
// Robot should safely stop
assertEquals(ERROR, wallApproach.getState());
assertEquals(0.0, motor.getPower());
assertTrue(wallApproach.hasSensorError());
}
```
**What this tests:**
- Error detection
- Safe shutdown procedures
- Graceful degradation
- Diagnostic reporting
**Time to run:** ~2 milliseconds
**Why this matters:**
- Can't safely disconnect sensors on real robot during testing
- Would risk crashing robot into wall
- Tests this scenario hundreds of times instantly
### 4. Integration Tests (System-Level)
Test multiple subsystems interacting realistically.
**Example: Square Pattern Navigation**
```java
@Test
void testSquarePattern() {
for (int side = 1; side <= 4; side++) {
// Drive forward to wall
distanceSensor.setDistance(50.0);
wallApproach.start();
simulateDriving();
// Turn 90° right
turnController.turnTo(side * 90);
simulateTurning();
}
// Should complete square and face original direction
assertTrue(Math.abs(gyro.getHeading()) <= 2.0);
}
```
**What this tests:**
- Sequential operations
- Repeated patterns
- Accumulated errors
- Return to starting position
**Time to run:** ~200 milliseconds
## Real-World Scenarios You Can Test
### Scenario 1: Approaching Moving Target
```java
@Test
void testApproachingMovingTarget() {
distanceSensor.setDistance(100.0);
wallApproach.start();
for (int i = 0; i < 50; i++) {
wallApproach.update();
// Target is also moving away!
distanceSensor.approach(motor.getPower() * 2.0 - 0.5);
// Robot should still eventually catch up
}
assertTrue(distanceSensor.getDistanceCm() < 15.0);
}
```
### Scenario 2: Noisy Sensor Data
```java
@Test
void testHandlesNoisySensors() {
sensor.setNoise(2.0); // ±2cm random jitter
sensor.setDistance(50.0);
wallApproach.start();
// Run 100 updates with noisy data
for (int i = 0; i < 100; i++) {
wallApproach.update();
sensor.approach(0.5);
// Should not oscillate wildly or crash
assertTrue(motor.getPower() >= 0);
assertTrue(motor.getPower() <= 1.0);
}
}
```
### Scenario 3: Gyro Drift Compensation
```java
@Test
void testCompensatesForGyroDrift() {
gyro.setHeading(0);
gyro.setDrift(0.5); // 0.5° per second drift
turnController.turnTo(90);
// Simulate turn with drift
for (int i = 0; i < 100; i++) {
turnController.update();
gyro.rotate(motor.getPower() * 2.0);
Thread.sleep(10); // Let drift accumulate
}
// Should still reach target despite drift
assertTrue(Math.abs(gyro.getHeading() - 90) <= 2.0);
}
```
### Scenario 4: Battery Voltage Drop
```java
@Test
void testLowBatteryCompensation() {
MockBattery battery = new MockBattery();
MotorController motor = new VoltageCompensatedMotor(battery);
// Full battery
battery.setVoltage(12.5);
motor.setPower(0.5);
assertEquals(0.5, motor.getActualPower());
// Low battery
battery.setVoltage(11.0);
motor.setPower(0.5);
assertTrue(motor.getActualPower() > 0.5, // Compensated up
"Should increase power to compensate for voltage drop");
}
```
## Testing Benefits
### Speed
- **41 tests run in < 2 seconds**
- No deployment time
- No robot setup time
- Instant feedback
### Reliability
- Test edge cases safely
- Test failure modes
- Test thousands of scenarios
- Catch bugs before robot time
### Confidence
- Know code works before deploying
- Automated regression testing
- Safe refactoring
- Professional quality
### Learning
- Students learn professional practices
- Industry-standard patterns
- Test-driven development
- Debugging without hardware
## Test Metrics
```
Total Tests: 41
- MotorCyclerTest: 8 tests
- WallApproachTest: 13 tests
- TurnControllerTest: 15 tests
- AutonomousIntegrationTest: 5 tests
Total Runtime: < 2 seconds
Lines of Test Code: ~1,200
Lines of Production Code: ~500
Test Coverage: Excellent
Bugs Caught Before Robot Testing: Countless!
```
## The Pattern for Students
Teaching students this approach gives them:
1. **Immediate feedback** - No waiting for robot
2. **Safe experimentation** - Can't break robot in tests
3. **Professional skills** - Industry-standard practices
4. **Better code** - Testable code is well-designed code
5. **Confidence** - Know it works before deploying
## Comparison
### Traditional FTC Development
```
Write code (10 min)
Deploy to robot (5 min)
Test on robot (10 min)
Find bug
Repeat...
Time per iteration: ~25 minutes
Bugs found: Late (on robot)
Risk: High (can damage robot)
```
### With Testing
```
Write code (10 min)
Run tests (2 sec)
Fix bugs immediately (5 min)
Deploy confident code (5 min)
Works on robot!
Time per iteration: ~20 minutes (first deploy!)
Bugs found: Early (in tests)
Risk: Low (robot rarely crashes)
```
## Advanced Testing Patterns
### Parameterized Tests
Test the same logic with different inputs:
```java
@ParameterizedTest
@ValueSource(doubles = {10, 20, 30, 40, 50})
void testDifferentStopDistances(double distance) {
sensor.setDistance(100.0);
wallApproach = new WallApproach(sensor, motor, distance);
wallApproach.start();
simulateDriving();
assertTrue(sensor.getDistanceCm() <= distance + 2);
}
```
### State Machine Verification
Test all state transitions:
```java
@Test
void testAllStateTransitions() {
// INIT → APPROACHING
wallApproach.start();
assertEquals(APPROACHING, wallApproach.getState());
// APPROACHING → SLOWING
sensor.setDistance(25.0);
wallApproach.update();
assertEquals(SLOWING, wallApproach.getState());
// SLOWING → STOPPED
sensor.setDistance(10.0);
wallApproach.update();
assertEquals(STOPPED, wallApproach.getState());
// STOPPED → STOPPED (stays stopped)
wallApproach.update();
assertEquals(STOPPED, wallApproach.getState());
}
```
### Performance Testing
Verify code runs fast enough:
```java
@Test
void testUpdatePerformance() {
long startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
wallApproach.update();
}
long elapsedMs = (System.nanoTime() - startTime) / 1_000_000;
assertTrue(elapsedMs < 100,
"1000 updates should complete in < 100ms");
}
```
## Conclusion
Testing without hardware is **not a compromise** - it's actually **better**:
- Faster development
- Safer testing
- More thorough coverage
- Professional practices
This is how real robotics companies (Boston Dynamics, Tesla, SpaceX) develop robots.
Your students are learning the same techniques used to land rockets and build autonomous vehicles!