Files
spacetris/docs/TESTING_STRATEGY.md
Gregor Klevze 66099809e0 feat: implement textured line clear effects and refine UI alignment
- **Visual Effects**: Upgraded line clear particles to use the game's block texture instead of simple circles, matching the reference web game's aesthetic.
- **Particle Physics**: Tuned particle velocity, gravity, and fade rates for a more dynamic explosion effect.
- **Rendering Integration**: Updated [main.cpp](cci:7://file:///d:/Sites/Work/tetris/src/main.cpp:0:0-0:0) and `GameRenderer` to pass the block texture to the effect system and correctly trigger animations upon line completion.
- **Menu UI**: Fixed [MenuState](cci:1://file:///d:/Sites/Work/tetris/src/states/MenuState.cpp:19:0-19:55) layout calculations to use fixed logical dimensions (1200x1000), ensuring consistent centering and alignment of the logo, buttons, and settings icon across different window sizes.
- **Code Cleanup**: Refactored `PlayingState` to delegate effect triggering to the rendering layer where correct screen coordinates are available.
2025-11-21 21:19:14 +01:00

7.7 KiB

Testing Strategy Enhancement

Current Testing State

Existing Tests

  • GravityTests.cpp - Basic gravity manager testing
  • Catch2 framework integration
  • CTest integration in CMake

Coverage Gaps

  • Game logic testing (piece movement, rotation, line clearing)
  • Collision detection testing
  • Scoring system testing
  • State management testing
  • Integration tests
  • Performance tests

Comprehensive Testing Strategy

1. Unit Tests Expansion

// tests/GameLogicTests.cpp
TEST_CASE("Piece Movement", "[game][movement]") {
    Game game(0);
    Piece originalPiece = game.current();
    
    SECTION("Move left when possible") {
        game.move(-1);
        REQUIRE(game.current().x == originalPiece.x - 1);
    }
    
    SECTION("Cannot move left at boundary") {
        // Move piece to left edge
        while (game.current().x > 0) {
            game.move(-1);
        }
        int edgeX = game.current().x;
        game.move(-1);
        REQUIRE(game.current().x == edgeX); // Should not move further
    }
}

// tests/CollisionTests.cpp
TEST_CASE("Collision Detection", "[game][collision]") {
    Game game(0);
    
    SECTION("Piece collides with bottom") {
        // Force piece to bottom
        while (!game.isGameOver()) {
            game.hardDrop();
            if (game.isGameOver()) break;
        }
        // Verify collision behavior
    }
    
    SECTION("Piece collides with placed blocks") {
        // Place a block manually
        // Test collision with new piece
    }
}

// tests/ScoringTests.cpp
TEST_CASE("Scoring System", "[game][scoring]") {
    Game game(0);
    int initialScore = game.score();
    
    SECTION("Single line clear") {
        // Set up board with almost complete line
        // Clear line and verify score increase
    }
    
    SECTION("Tetris (4 lines)") {
        // Set up board for Tetris
        // Verify bonus scoring
    }
}

2. Mock Objects for Testing

// tests/mocks/MockRenderer.h
class MockRenderer : public IRenderer {
private:
    mutable std::vector<std::string> calls;
    
public:
    void clear(uint8_t r, uint8_t g, uint8_t b, uint8_t a) override {
        calls.push_back("clear");
    }
    
    void present() override {
        calls.push_back("present");
    }
    
    SDL_Renderer* getSDLRenderer() override {
        return nullptr; // Mock implementation
    }
    
    const std::vector<std::string>& getCalls() const { return calls; }
    void clearCalls() { calls.clear(); }
};

// tests/mocks/MockAudioSystem.h
class MockAudioSystem : public IAudioSystem {
private:
    std::vector<std::string> playedSounds;
    
public:
    void playSound(const std::string& name) override {
        playedSounds.push_back(name);
    }
    
    void playMusic(const std::string& name) override {
        playedSounds.push_back("music:" + name);
    }
    
    void setMasterVolume(float volume) override {
        // Mock implementation
    }
    
    const std::vector<std::string>& getPlayedSounds() const { return playedSounds; }
};

3. Integration Tests

// tests/integration/StateTransitionTests.cpp
TEST_CASE("State Transitions", "[integration][states]") {
    ApplicationManager app;
    // Mock dependencies
    
    SECTION("Loading to Menu transition") {
        // Simulate loading completion
        // Verify menu state activation
    }
    
    SECTION("Menu to Game transition") {
        // Simulate start game action
        // Verify game state initialization
    }
}

// tests/integration/GamePlayTests.cpp
TEST_CASE("Complete Game Session", "[integration][gameplay]") {
    Game game(0);
    
    SECTION("Play until first line clear") {
        // Simulate complete game session
        // Verify all systems work together
    }
}

4. Performance Tests

// tests/performance/PerformanceTests.cpp
TEST_CASE("Game Logic Performance", "[performance]") {
    Game game(0);
    
    SECTION("1000 piece drops should complete in reasonable time") {
        auto start = std::chrono::high_resolution_clock::now();
        
        for (int i = 0; i < 1000; ++i) {
            game.hardDrop();
            if (game.isGameOver()) {
                game.reset(0);
            }
        }
        
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
        
        REQUIRE(duration.count() < 100); // Should complete in under 100ms
    }
}

// tests/performance/MemoryTests.cpp
TEST_CASE("Memory Usage", "[performance][memory]") {
    SECTION("No memory leaks during gameplay") {
        size_t initialMemory = getCurrentMemoryUsage();
        
        {
            Game game(0);
            // Simulate gameplay
            for (int i = 0; i < 100; ++i) {
                game.hardDrop();
                if (game.isGameOver()) game.reset(0);
            }
        }
        
        size_t finalMemory = getCurrentMemoryUsage();
        REQUIRE(finalMemory <= initialMemory + 1024); // Allow small overhead
    }
}

5. Property-Based Testing

// tests/property/PropertyTests.cpp
TEST_CASE("Property: Game state consistency", "[property]") {
    Game game(0);
    
    SECTION("Score never decreases") {
        int previousScore = game.score();
        
        // Perform random valid actions
        for (int i = 0; i < 100; ++i) {
            performRandomValidAction(game);
            REQUIRE(game.score() >= previousScore);
            previousScore = game.score();
        }
    }
    
    SECTION("Board state remains valid") {
        for (int i = 0; i < 1000; ++i) {
            performRandomValidAction(game);
            REQUIRE(isBoardStateValid(game));
        }
    }
}

6. Test Data Management

// tests/fixtures/GameFixtures.h
class GameFixtures {
public:
    static Game createGameWithAlmostFullLine() {
        Game game(0);
        // Set up specific board state
        return game;
    }
    
    static Game createGameNearGameOver() {
        Game game(0);
        // Fill board almost to top
        return game;
    }
    
    static std::vector<PieceType> createTetrisPieceSequence() {
        return {I, O, T, S, Z, J, L};
    }
};

Test Automation & CI

1. GitHub Actions Configuration

# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [windows-latest, ubuntu-latest, macos-latest]
        build-type: [Debug, Release]
    
    steps:
    - uses: actions/checkout@v3
    - name: Install dependencies
      run: |
        # Install vcpkg and dependencies
    - name: Configure CMake
      run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }}
    - name: Build
      run: cmake --build build --config ${{ matrix.build-type }}
    - name: Test
      run: ctest --test-dir build --build-config ${{ matrix.build-type }}

2. Code Coverage

# Add to CMakeLists.txt
option(ENABLE_COVERAGE "Enable code coverage" OFF)

if(ENABLE_COVERAGE)
    target_compile_options(tetris PRIVATE --coverage)
    target_link_libraries(tetris PRIVATE --coverage)
endif()

Quality Metrics Targets

  • Unit Test Coverage: > 80%
  • Integration Test Coverage: > 60%
  • Performance Regression: < 5% per release
  • Memory Leak Detection: 0 leaks in test suite
  • Static Analysis: 0 critical issues

Implementation Priority

  1. Phase 1: Core game logic unit tests (movement, rotation, collision)
  2. Phase 2: Mock objects and dependency injection for testability
  3. Phase 3: Integration tests for state management
  4. Phase 4: Performance and memory tests
  5. Phase 5: Property-based testing and fuzzing
  6. Phase 6: CI/CD pipeline with automated testing