Skip to content

๐Ÿงช Dual Testing Strategy - Google Test + Catch2

Created: 2025-10-17 Status: ACTIVE โœ… Approach: Professional Dual Framework


๐ŸŽฏ Philosophy: Best Tool for Each Job

AudioLab uses TWO testing frameworks in a complementary approach: - Google Test (GTest) - Legacy tests, complex fixtures, mature ecosystem - Catch2 - New tests, BDD style, modern C++20

Why Dual? - โœ… Leverage strengths of both frameworks - โœ… No need to rewrite 74+ existing GTest tests - โœ… Use modern Catch2 for new components - โœ… Cross-validation: critical features tested by BOTH


๐Ÿ“Š Current State

Google Test (GTest)

  • Tests: 74+ existing tests
  • Coverage: All 16 CORE subsystems
  • Status: Installed via vcpkg
  • Use Cases: Existing tests, complex fixtures

Catch2

  • Tests: 0 (new framework)
  • Coverage: Future tests
  • Status: Installed via vcpkg
  • Use Cases: New tests, BDD scenarios

๐Ÿ”ง When to Use Each Framework

Use Google Test When:

  1. Working with existing tests

    // 74+ existing tests already use GTest
    #include <gtest/gtest.h>
    TEST(RingBufferTest, BasicOperations) { ... }
    

  2. Complex test fixtures needed

    class AudioProcessorTest : public ::testing::Test {
    protected:
        void SetUp() override { /* complex setup */ }
        void TearDown() override { /* cleanup */ }
        // Shared test data
    };
    

  3. Parameterized tests

    class BufferSizeTest : public ::testing::TestWithParam<int> {};
    INSTANTIATE_TEST_SUITE_P(Sizes, BufferSizeTest,
        ::testing::Values(64, 128, 256, 512));
    

  4. Death tests (testing crashes/assertions)

    ASSERT_DEATH(BadFunction(), "assertion failed");
    

  5. Google Mock integration

    #include <gmock/gmock.h>
    MOCK_METHOD(void, ProcessAudio, (float* buffer));
    


Use Catch2 When:

  1. Writing new tests

    #include <catch2/catch_test_macros.hpp>
    TEST_CASE("Parameter smoothing works correctly", "[parameter]") {
        REQUIRE(param.getValue() == Approx(0.5f));
    }
    

  2. BDD-style scenarios

    SCENARIO("User adjusts filter cutoff", "[dsp][filter]") {
        GIVEN("A lowpass filter at 1kHz") {
            WHEN("User changes to 2kHz") {
                THEN("Filter updates smoothly") {
                    REQUIRE(filter.getCutoff() == Approx(2000.0f));
                }
            }
        }
    }
    

  3. Simple, readable tests

    TEST_CASE("AudioBuffer allocates correctly") {
        AudioBuffer buffer(512, 2);
        REQUIRE(buffer.getNumSamples() == 512);
        REQUIRE(buffer.getNumChannels() == 2);
    }
    

  4. Sections for setup variations

    TEST_CASE("SIMD operations") {
        Vec4f a{1,2,3,4};
    
        SECTION("Addition works") {
            Vec4f result = a + Vec4f{1,1,1,1};
            REQUIRE(result[0] == Approx(2.0f));
        }
    
        SECTION("Multiplication works") {
            Vec4f result = a * 2.0f;
            REQUIRE(result[0] == Approx(2.0f));
        }
    }
    

  5. Generators and property-based testing

    TEST_CASE("Fast sin approximation") {
        auto angle = GENERATE(take(100, random(0.0f, 2.0f * PI)));
        REQUIRE(fast_sin(angle) == Approx(std::sin(angle)).margin(0.001f));
    }
    


๐Ÿ“ File Organization

Directory Structure

04_CORE/
โ”œโ”€โ”€ 04_00_type_system/
โ”‚   โ”œโ”€โ”€ tests/
โ”‚   โ”‚   โ”œโ”€โ”€ test_simd_operations.cpp        # GTest (existing)
โ”‚   โ”‚   โ”œโ”€โ”€ test_audio_buffer.cpp            # GTest (existing)
โ”‚   โ”‚   โ””โ”€โ”€ catch2_buffer_scenarios.cpp      # Catch2 (new)
โ”‚   โ””โ”€โ”€ CMakeLists.txt

Naming Conventions

Google Test files: - test_*.cpp - Existing GTest tests - Example: test_containers.cpp

Catch2 files: - catch2_*.cpp - New Catch2 tests - Example: catch2_parameter_scenarios.cpp

Benchmark files (Google Benchmark): - bench_*.cpp - Performance tests - Example: bench_ringbuffer.cpp


๐Ÿ”— CMake Integration

Root CMakeLists.txt

# Enable testing
option(BUILD_TESTING "Build tests" ON)

if(BUILD_TESTING)
    enable_testing()

    # Find both frameworks
    find_package(GTest CONFIG REQUIRED)
    find_package(Catch2 3 CONFIG REQUIRED)

    # Include Catch2 extras
    include(Catch)

    # Add test subdirectories
    add_subdirectory(04_CORE)
endif()

Per-Component CMakeLists.txt

# Google Test executable
add_executable(test_ringbuffer_gtest
    tests/test_ringbuffer.cpp
)
target_link_libraries(test_ringbuffer_gtest
    PRIVATE
        audiolab_core
        GTest::gtest_main
)
gtest_discover_tests(test_ringbuffer_gtest)

# Catch2 executable
add_executable(test_ringbuffer_catch2
    tests/catch2_ringbuffer_scenarios.cpp
)
target_link_libraries(test_ringbuffer_catch2
    PRIVATE
        audiolab_core
        Catch2::Catch2WithMain
)
catch_discover_tests(test_ringbuffer_catch2)

๐ŸŽฏ Critical Components: Dual Coverage

For mission-critical components, write tests in BOTH frameworks:

Example: RingBuffer

GTest - Unit tests:

// test_ringbuffer.cpp
TEST(RingBufferTest, WriteRead) {
    RingBuffer<float, 64> rb;
    rb.write(42.0f);
    EXPECT_EQ(rb.read(), 42.0f);
}

Catch2 - Scenarios:

// catch2_ringbuffer_scenarios.cpp
SCENARIO("Audio thread uses ring buffer", "[realtime]") {
    GIVEN("A ring buffer for audio samples") {
        RingBuffer<float, 512> rb;

        WHEN("Audio thread writes 256 samples") {
            for(int i = 0; i < 256; ++i) rb.write(float(i));

            THEN("All samples can be read back") {
                for(int i = 0; i < 256; ++i) {
                    REQUIRE(rb.read() == Approx(float(i)));
                }
            }
        }
    }
}

Components Requiring Dual Coverage

  • โœ… RingBuffer (lock-free, critical)
  • โœ… Parameter smoothing (audio quality)
  • โœ… AudioProcessor (plugin core)
  • โœ… Fast math (DSP accuracy)
  • โœ… Memory allocators (realtime safety)

๐Ÿ“Š Test Metrics

Current Stats

Framework Tests Coverage Status
GTest 74 100% subsystems โœ… Existing
Catch2 0 0% ๐Ÿ†• Ready
TOTAL 74 16/16 subsystems ๐Ÿ”„ In Progress

Target Stats (Q1 2026)

Framework Tests Coverage Status
GTest 74 Legacy tests โœ… Maintained
Catch2 100+ New features ๐ŸŽฏ Target
Dual 20 Critical paths ๐ŸŽฏ Target
TOTAL 194+ 100% critical ๐Ÿš€ Goal

๐Ÿ”„ Migration Strategy

Short Term (2025 Q4)

  • โœ… Install both frameworks
  • โœ… Integrate both in CMake
  • โœ… Document dual strategy
  • ๐Ÿ”„ Keep all 74 GTest tests as-is
  • ๐Ÿ†• Write new tests in Catch2

Medium Term (2026 Q1-Q2)

  • ๐ŸŽฏ Add Catch2 scenarios for critical components
  • ๐ŸŽฏ Achieve dual coverage on top 20 components
  • ๐ŸŽฏ Create BDD scenarios for user-facing features

Long Term (2026 Q3+)

  • ๐Ÿค” Evaluate: Keep dual or standardize?
  • ๐Ÿค” Option A: Keep dual (if both provide value)
  • ๐Ÿค” Option B: Migrate GTest โ†’ Catch2 (if Catch2 proves superior)
  • ๐Ÿค” Decision based on team preference + metrics

๐Ÿ› ๏ธ Running Tests

Run All Tests

cd _ARTIFACTS/build
ctest -C Debug -VV

Run Only GTest

ctest -C Debug -R ".*_gtest" -VV

Run Only Catch2

ctest -C Debug -R ".*_catch2" -VV

Run Specific Component

ctest -C Debug -R "RingBuffer" -VV  # Both GTest and Catch2

Run with Google Test filters

./_ARTIFACTS/build/test_ringbuffer_gtest --gtest_filter=RingBuffer*

Run with Catch2 tags

./_ARTIFACTS/build/test_ringbuffer_catch2 -t "[realtime]"

๐Ÿ“ Best Practices

1. Tag Your Tests

// Catch2 - Use tags for organization
TEST_CASE("Fast sine", "[math][dsp][realtime]") { }

// GTest - Use test suite names
TEST(FastMathTest, Sine) { }

2. Consistent Naming

// Component: RingBuffer
// GTest:   test_ringbuffer.cpp      โ†’ test_ringbuffer_gtest
// Catch2:  catch2_ringbuffer.cpp    โ†’ test_ringbuffer_catch2

3. Share Test Data

// test_data/ringbuffer_fixtures.hpp
namespace test_fixtures {
    constexpr size_t BUFFER_SIZE = 512;
    const std::vector<float> SAMPLE_DATA = {/* ... */};
}

// Use in both GTest and Catch2

4. Document Framework Choice

// WHY GTest: Complex fixture with shared state
// WHY Catch2: BDD scenario for user workflow

๐ŸŽ“ Learning Resources

Google Test

Catch2


โœ… Decision Matrix

Feature GTest Catch2 Winner
Existing tests โœ… 74 tests โŒ 0 tests GTest
Modern C++20 โš ๏ธ Good โœ… Excellent Catch2
BDD scenarios โŒ No โœ… Yes Catch2
Complex fixtures โœ… Excellent โš ๏ธ Good GTest
Mocking โœ… GMock โš ๏ธ External GTest
Readability โš ๏ธ Good โœ… Excellent Catch2
Maturity โœ… Very mature โœ… Mature Tie
Community โœ… Huge โœ… Large Tie

Conclusion: Use BOTH! ๐ŸŽฏ


๐Ÿ“ž Contacts

  • Testing Lead: AudioLab Core Team
  • GTest Questions: See existing tests in 04_CORE/*/tests/
  • Catch2 Questions: See examples in 03_INFRA/03_04_testing_framework/examples/

Version: 1.0.0 Last Updated: 2025-10-17 Status: ACTIVE - Dual Framework Approach โœ