Skip to content

Testing

James Maes edited this page Aug 27, 2025 · 4 revisions

Testing

QQQ enforces strict testing standards to ensure framework quality and reliability. This guide covers testing strategies, tools, and requirements for QQQ framework development.

Testing Philosophy

Quality-First Approach

QQQ treats testing as a quality gate, not an afterthought:

# Testing is integrated into the build process
mvn clean install  # Runs tests + coverage + validation
mvn test          # Just tests
mvn verify        # Tests + coverage checks

Core Principles:

  • Test-Driven Development: Write tests before or alongside code
  • Comprehensive Coverage: 80% instruction, 95% class coverage required
  • Real Testing: Use actual QQQ components, not mocks
  • Integration Focus: Test how components work together

Testing Pyramid

QQQ follows a balanced testing approach:

    /\
   /  \     E2E Tests (Sample Project)
  /____\    
 /      \   Integration Tests (Cross-module)
/________\  
Unit Tests (Individual components)

Testing Tools and Infrastructure

Testing Framework Stack

QQQ uses modern Java testing tools:

  • JUnit 5: Modern testing framework with lifecycle hooks
  • AssertJ: Fluent assertions for readable test code
  • JaCoCo: Code coverage analysis and enforcement
  • Maven Surefire: Test execution and reporting

Test Infrastructure

QQQ provides comprehensive testing infrastructure:

// βœ… CORRECT - Extending QQQ's BaseTest
public class MyModuleTest extends BaseTest
{
   @Test
   void testMyFeature()
   {
      // BaseTest provides:
      // - QContext with test QInstance
      // - Memory backend for isolated testing
      // - Test session and user setup
      // - Automatic cleanup after each test
      
      // Your test logic here
   }
}

Coverage Requirements

Strict Coverage Thresholds

QQQ enforces coverage requirements that must be met:

<coverage.haltOnFailure>true</coverage.haltOnFailure>
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>

Coverage Rules:

  • Instructions: 80% minimum (build fails if below)
  • Classes: 95% minimum (build fails if below)
  • Lines: Monitored but not enforced
  • Branches: Monitored but not enforced

Coverage Enforcement

Coverage is checked automatically:

# Check coverage without failing build
mvn jacoco:report

# Full build with coverage enforcement
mvn clean install

# Coverage report location
open target/site/jacoco/index.html

Testing Patterns

Unit Testing

Test individual components in isolation:

@Test
void testActionExecution()
{
   // Test action logic
   MyAction action = new MyAction();
   MyInput input = new MyInput("test");
   MyOutput output = action.execute(input);
   
   // Verify results
   assertThat(output.getResult()).isEqualTo("expected");
}

Integration Testing

Test how components work together:

@Test
void testBackendIntegration()
{
   // Test backend module with QQQ core
   QInstance instance = TestUtils.defineInstance();
   instance.addBackend(new RDBMSBackend());
   
   // Verify integration
   assertThat(instance.getBackends()).hasSize(1);
}

Framework Testing

Test QQQ framework extensions:

@Test
void testNewMetadataType()
{
   // Test new metadata integration
   QInstance instance = TestUtils.defineInstance();
   instance.addMetaData(new MyNewMetaData());
   
   // Verify metadata is properly integrated
   assertThat(instance.getMetaData(MyNewMetaData.class)).isNotNull();
}

Test Data Management

TestUtils Helper

QQQ provides utilities for test data:

// Create test QInstance
QInstance instance = TestUtils.defineInstance();

// Create test session
QSession session = TestUtils.newSession();

// Create test records
QRecord record = TestUtils.createRecord("person", Map.of("name", "John"));

Memory Backend

Use in-memory storage for isolated testing:

@Test
void testWithMemoryBackend()
{
   // Memory backend provides isolated storage
   QInstance instance = TestUtils.defineInstance();
   instance.addBackend(new MemoryBackend());
   
   // Tests run in isolation
   // No external dependencies
   // Fast execution
}

Test Organization

Package Structure

Organize tests to match source structure:

src/test/java/
β”œβ”€β”€ com/kingsrook/qqq/
β”‚   β”œβ”€β”€ actions/
β”‚   β”‚   └── MyActionTest.java
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── MyModelTest.java
β”‚   └── modules/
β”‚       └── MyModuleTest.java

Naming Conventions

Follow QQQ test naming patterns:

// Test class names
public class MyActionTest extends BaseTest
public class MyModuleIntegrationTest extends BaseTest

// Test method names
@Test
void testActionExecution()
@Test
void testActionWithInvalidInput()
@Test
void testActionIntegration()

Performance Testing

Load Testing

Test framework performance under load:

@Test
void testConcurrentActionExecution()
{
   // Test multiple concurrent actions
   ExecutorService executor = Executors.newFixedThreadPool(10);
   List<Future<MyOutput>> futures = new ArrayList<>();
   
   for (int i = 0; i < 100; i++)
   {
      futures.add(executor.submit(() -> action.execute(input)));
   }
   
   // Verify all actions complete successfully
   futures.forEach(future -> assertThat(future.get()).isNotNull());
}

Memory Testing

Monitor memory usage in tests:

@Test
void testMemoryUsage()
{
   // Monitor memory before
   long memoryBefore = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
   
   // Execute action
   action.execute(input);
   
   // Monitor memory after
   long memoryAfter = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
   
   // Verify reasonable memory usage
   assertThat(memoryAfter - memoryBefore).isLessThan(10 * 1024 * 1024); // 10MB
}

Test Execution

Local Testing

Run tests during development:

# Run all tests
mvn test

# Run specific module tests
mvn test -pl qqq-backend-core

# Run specific test class
mvn test -Dtest=MyActionTest

# Run specific test method
mvn test -Dtest=MyActionTest#testActionExecution

CI/CD Integration

Tests run automatically in CI:

# CircleCI runs tests on every commit
# Coverage is enforced
# Build fails if coverage drops below thresholds

Troubleshooting

Common Test Issues

# Coverage failure
mvn jacoco:report  # Check coverage report

# Test timeout
mvn test -Dtest.timeout=300  # Increase timeout

# Memory issues
mvn test -Dtest.maxMemory=2g  # Increase memory

Test Debugging

# Run with debug output
mvn test -X

# Run single test with debug
mvn test -Dtest=MyActionTest -DtestOutputToFile=false

# Check test output files
ls -la target/surefire-reports/

Best Practices

Test Design

  • Arrange-Act-Assert: Structure tests clearly
  • Test Names: Use descriptive test method names
  • Single Responsibility: Each test verifies one thing
  • Real Data: Use realistic test data

Test Maintenance

  • Keep Tests Fast: Tests should run quickly
  • Isolate Tests: Tests should not depend on each other
  • Clean Setup: Use @BeforeEach and @AfterEach properly
  • Update Tests: Keep tests current with code changes

For development setup, see Developer Onboarding. For code standards, see Code Review Standards. For build process, see Building Locally.

Clone this wiki locally