Back to Blog

Testing Pyramid: Quality Assurance for a Multi-Language Platform

TL;DR

CoomiaPublished on July 7, 202517 min read
Share this articleTwitter / X

Testing Pyramid: Quality Assurance for a Multi-Language Platform

Series: S2 Architecture Overview · Article 14 | Level: Intermediate | Reading Time: 18 min

TL;DR

  • coomia-dip is a platform built on a Java + Python mixed technology stack. Its testing strategy must simultaneously cover unit tests, integration tests, and end-to-end tests across both languages. The testing pyramid ensures 80% of tests complete in milliseconds, while the remaining 20% of high-value tests verify cross-Layer integration.
  • gRPC interfaces form the platform's internal communication backbone and serve as the core testing boundary. Every Layer's gRPC service must have independent contract tests to ensure cross-language (Java to Python) interface compatibility.
  • Scenario Testing is coomia-dip's signature quality assurance methodology. Rather than testing individual functions or endpoints, it simulates complete business scenarios (e.g., "create object, trigger derived property, verify cascade update"), validating correctness when multiple Layers work together.

#1. Introduction: Why Traditional Testing Strategies Fall Short

#1.1 Testing Challenges in a Multi-Language Platform

coomia-dip is not a single-technology-stack project. Its 8 Layers span two primary languages:

Code
Java Layers:
  Control Layer (Control Layer)     → Spring Boot 3.x, Java 21
  Data Layer (Data Layer)        → Quarkus 3.x, Java 21

Python Layers:
  Reasoning & Decision Layer (Reasoning)         → FastAPI, Python 3.x
  Agent Runtime Layer (Agent Runtime)     → FastAPI, Temporal, Python 3.x

Cross-language interfaces:
  SDK & Developer Experience Layer (SDK)               → Python SDK + TypeScript SDK
  Deployment & Operations Layer (Deployment)        → Docker Compose, Python scripts

The traditional testing pyramid assumes a homogeneous test target -- all code uses the same language, the same build tool, the same test framework. But in coomia-dip:

  • Java tests use JUnit 5 + Mockito + Testcontainers
  • Python tests use pytest + unittest.mock
  • Cross-language integration tests require both Java and Python services running simultaneously
  • gRPC interfaces require cross-language compatibility verification

#1.2 The coomia-dip Testing Pyramid

We adapted the classic testing pyramid:

Code
                    ╱╲
                   ╱  ╲
                  ╱ E2E╲         ← 5% | Scenario-based end-to-end tests
                 ╱──────╲          Cross-Layer full-chain verification
                ╱Integration╲   ← 15% | gRPC contract tests + DB integration
               ╱──────────────╲    Single-Layer internal integration
              ╱  Unit Tests     ╲← 80% | Domain logic, utility functions
             ╱────────────────────╲  Pure functions, no external dependencies

#2. Layer 1: Unit Tests

#2.1 Java Unit Tests (Control Layer + Data Layer)

Java Layer unit tests use JUnit 5 + Mockito:

Java
// Control Layer unit test example
@ExtendWith(MockitoExtension.class)
class OntologyTypeServiceTest {

    @Mock
    private OntologyTypeRepository repository;

    @Mock
    private EventPublisher eventPublisher;

    @InjectMocks
    private OntologyTypeService service;

    @Test
    @DisplayName("Should validate property constraints when creating ObjectType")
    void shouldValidatePropertyConstraints() {
        // Given
        var request = CreateObjectTypeRequest.newBuilder()
            .setName("Supplier")
            .addProperties(PropertyDef.newBuilder()
                .setName("name")
                .setType(PropertyType.STRING)
                .setRequired(true)
                .build())
            .build();

        when(repository.existsByName("Supplier")).thenReturn(false);

        // When
        var result = service.createObjectType(request);

        // Then
        assertThat(result.getName()).isEqualTo("Supplier");
        verify(eventPublisher).publish(any(ObjectTypeCreatedEvent.class));
    }

    @Test
    @DisplayName("Should reject duplicate names")
    void shouldRejectDuplicateName() {
        var request = CreateObjectTypeRequest.newBuilder()
            .setName("ExistingType")
            .build();

        when(repository.existsByName("ExistingType")).thenReturn(true);

        assertThrows(DuplicateTypeException.class,
            () -> service.createObjectType(request));
    }
}

#2.2 Python Unit Tests (Reasoning & Decision Layer + Agent Runtime Layer)

Python Layer unit tests use pytest:

Python
# Intelligence Layer unit test example
import pytest
from unittest.mock import AsyncMock, patch
from decimal import Decimal

from reasoning_engine.core.rule_evaluator import RuleEvaluator
from reasoning_engine.models.rule import Rule, Condition, Action


class TestRuleEvaluator:
    """Rule evaluator unit tests"""

    @pytest.fixture
    def evaluator(self):
        return RuleEvaluator()

    @pytest.fixture
    def simple_rule(self):
        return Rule(
            name="high_risk_supplier",
            conditions=[
                Condition(field="risk_score", operator=">=", value=0.8),
                Condition(field="active", operator="==", value=True),
            ],
            actions=[
                Action(type="set_label", params={"label": "HIGH_RISK"}),
            ],
        )

    def test_rule_matches_when_all_conditions_met(
        self, evaluator, simple_rule
    ):
        context = {"risk_score": 0.85, "active": True}
        result = evaluator.evaluate(simple_rule, context)

        assert result.matched is True
        assert result.actions[0].type == "set_label"

    def test_rule_not_matched_when_condition_fails(
        self, evaluator, simple_rule
    ):
        context = {"risk_score": 0.5, "active": True}
        result = evaluator.evaluate(simple_rule, context)

        assert result.matched is False
        assert result.actions == []

    def test_missing_field_raises_evaluation_error(
        self, evaluator, simple_rule
    ):
        context = {"active": True}  # missing risk_score

        with pytest.raises(EvaluationError, match="Missing field: risk_score"):
            evaluator.evaluate(simple_rule, context)

#2.3 Unit Test Standards

RuleJavaPython
Coverage target>= 80%>= 80%
Naming conventionshouldXxxWhenYyytest_xxx_when_yyy
Mock frameworkMockitounittest.mock / pytest-mock
Assertion libraryAssertJpytest native assert
Test dataBuilder patternpytest.fixture
Execution time limit< 100ms per test< 100ms per test

#2.4 Prohibited Practices

The project's CLAUDE.md explicitly lists testing-related technical red lines:

Code
No deleting failing tests to "pass" the test suite
No empty catch blocks: catch(e) {}

Both rules share the same goal: no concealing problems. If a test fails, the correct response is to fix the code or update the test expectation -- never delete the test. Empty catch blocks follow the same logic: swallowing exceptions eliminates critical debugging information.

#3. Layer 2: Integration Tests

#3.1 gRPC Contract Tests

gRPC interfaces are the communication protocol between coomia-dip Layers. Contract tests ensure:

  • Protobuf message definitions remain compatible between producers and consumers
  • Adding new fields does not break existing consumers
  • Removing fields is caught at compile time
Python
# gRPC contract test example (Python SDK calling Java Control Layer)
import pytest
import grpc
from ontology_sdk.grpc_client import OntologyClient


class TestOntologyGrpcContract:
    """Verify Python SDK compatibility with Control Layer gRPC interface"""

    @pytest.fixture
    def client(self, grpc_channel):
        return OntologyClient(channel=grpc_channel)

    def test_create_object_type_contract(self, client):
        """Verify CreateObjectType request/response format"""
        response = client.create_object_type(
            name="TestType",
            properties=[
                {"name": "id", "type": "STRING", "required": True},
                {"name": "value", "type": "DECIMAL", "required": False},
            ],
        )

        # Verify response structure matches proto definition
        assert hasattr(response, "type_id")
        assert hasattr(response, "name")
        assert hasattr(response, "version")
        assert response.name == "TestType"

    def test_backward_compatibility(self, client):
        """Verify old-version requests still work"""
        # Requests without optional fields should succeed
        response = client.create_object_type(
            name="MinimalType",
            properties=[],  # Empty property list
        )
        assert response.type_id is not None

#3.2 Database Integration Tests

Java Layers use Testcontainers for database integration tests:

Java
@Testcontainers
@SpringBootTest
class OntologyRepositoryIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
        .withDatabaseName("ontology_test")
        .withUsername("test")
        .withPassword("test");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private OntologyTypeRepository repository;

    @Test
    @DisplayName("Saving and querying ObjectType should preserve data integrity")
    void shouldPersistAndRetrieveObjectType() {
        // Given
        var objectType = new ObjectTypeEntity();
        objectType.setName("Order");
        objectType.setVersion(1);

        // When
        var saved = repository.save(objectType);
        var retrieved = repository.findById(saved.getId());

        // Then
        assertThat(retrieved).isPresent();
        assertThat(retrieved.get().getName()).isEqualTo("Order");
    }
}

#3.3 Kafka Integration Tests

Event-driven architecture requires verifying Kafka message correctness:

Java
@EmbeddedKafka(topics = {"ontology.cdc.changes"})
@SpringBootTest
class EventPublishingIntegrationTest {

    @Autowired
    private EventPublisher publisher;

    @Autowired
    private KafkaTemplate<String, byte[]> kafkaTemplate;

    @Test
    @DisplayName("Object changes should publish CDC events to the correct topic")
    void shouldPublishCdcEventOnObjectChange() throws Exception {
        // Given
        var event = ObjectChangedEvent.newBuilder()
            .setObjectId("obj-001")
            .setObjectType("Supplier")
            .setChangeType(ChangeType.UPDATED)
            .build();

        // When
        publisher.publish(event);

        // Then
        var records = KafkaTestUtils.getRecords(consumer, Duration.ofSeconds(5));
        assertThat(records).hasSize(1);

        var published = ObjectChangedEvent.parseFrom(
            records.iterator().next().value()
        );
        assertThat(published.getObjectId()).isEqualTo("obj-001");
        assertThat(published.getChangeType()).isEqualTo(ChangeType.UPDATED);
    }
}

#4. Layer 3: End-to-End Tests

#4.1 Scenario Testing Methodology

coomia-dip's E2E tests adopt the Scenario Testing methodology (see /scenario-testing skill). The core idea: test complete business scenarios, not isolated API calls.

A typical scenario test:

Code
Scenario: Create supplier and verify automatic risk score computation

Preconditions:
  - Ontology has defined "Supplier" ObjectType
  - "risk_score" property is configured as a DerivedProperty
  - Computation rules are registered with the Intelligence Layer

Steps:
  1. Create Supplier object via SDK (name="Acme Corp")
  2. Set base properties (delivery_rate=0.75, complaint_count=3)
  3. Wait for derived property computation (max 5 seconds)
  4. Query the risk_score property value

Expected Results:
  - Supplier object created successfully
  - risk_score has been automatically computed
  - risk_score value is in the [0, 1] range
  - Audit log records the complete operation chain

#4.2 Python SDK Scenario Test Implementation

Python
# tests/scenarios/test_derived_property_lifecycle.py
import pytest
import time
from ontology_sdk import OntoPlatform


class TestDerivedPropertyLifecycle:
    """Derived property full lifecycle scenario test"""

    @pytest.fixture
    def platform(self):
        return OntoPlatform(
            host="localhost",
            port=8080,
            tenant_id="test-tenant",
        )

    def test_create_object_triggers_derived_computation(self, platform):
        """Scenario: Creating an object triggers automatic derived computation"""
        # Step 1: Create object
        supplier = platform.ontology.create_object(
            type_name="Supplier",
            properties={
                "name": "Acme Corp",
                "delivery_rate": 0.75,
                "complaint_count": 3,
            },
        )
        assert supplier.object_id is not None

        # Step 2: Wait for derived property computation (polling)
        max_wait = 5.0
        interval = 0.5
        elapsed = 0.0
        risk_score = None

        while elapsed < max_wait:
            obj = platform.ontology.get_object(
                type_name="Supplier",
                object_id=supplier.object_id,
                include_derived=True,
            )
            risk_score = obj.properties.get("risk_score")
            if risk_score is not None:
                break
            time.sleep(interval)
            elapsed += interval

        # Step 3: Verify results
        assert risk_score is not None, (
            f"Derived property not computed within {max_wait}s"
        )
        assert 0.0 <= float(risk_score) <= 1.0

        # Step 4: Verify audit log
        audit_logs = platform.audit.query(
            object_id=supplier.object_id,
            limit=10,
        )
        action_types = [log.action_type for log in audit_logs]
        assert "OBJECT_CREATED" in action_types
        assert "DERIVED_PROPERTY_COMPUTED" in action_types

    def test_update_triggers_cascade_recomputation(self, platform):
        """Scenario: Updating source properties triggers cascade recomputation"""
        # Create and wait for initial computation
        supplier = platform.ontology.create_object(
            type_name="Supplier",
            properties={"name": "Beta Inc", "delivery_rate": 0.9},
        )
        time.sleep(2)

        initial_score = platform.ontology.get_property(
            "Supplier", supplier.object_id, "risk_score"
        )

        # Update source property
        platform.ontology.update_object(
            type_name="Supplier",
            object_id=supplier.object_id,
            properties={"delivery_rate": 0.5},  # Significant drop
        )
        time.sleep(2)

        # Verify derived property was recomputed
        updated_score = platform.ontology.get_property(
            "Supplier", supplier.object_id, "risk_score"
        )

        assert updated_score != initial_score, (
            "Derived property should be recomputed after source change"
        )

#4.3 Cross-Layer Test Orchestration

E2E tests require multiple Layers running simultaneously. coomia-dip uses Docker Compose for test environment orchestration:

YAML
# docker-compose.test.yml
services:
  control-Layer:
    build: ./control-Layer
    ports: ["8080:8080", "9090:9090"]
    depends_on:
      - postgres
      - kafka

  intelligence-Layer:
    build: ./intelligence-Layer
    ports: ["8081:8081", "9091:9091"]
    depends_on:
      - control-Layer
      - kafka

  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: ontology_test

  kafka:
    image: confluentinc/cp-kafka:7.5.0

  redis:
    image: redis:7

#5. Cross-Language Testing Challenges

#5.1 Protobuf Version Compatibility

Java and Python share the same .proto files, but compiled artifacts must stay synchronized:

Code
proto/
├── ontology_service.proto    ← Single Source of Truth
├── action_service.proto
├── reasoning_service.proto
└── event_types.proto

Compilation flow:
  proto/ ──protoc──> control-Layer/src/gen/   (Java generated code)
  proto/ ──protoc──> python-sdk/ontology_sdk/proto/  (Python generated code)

Risk: If the Java side updates a proto file but forgets to recompile the Python side, runtime incompatibility occurs.

Solution: The CI pipeline automatically triggers dual-language compilation and contract tests on every proto file change.

#5.2 Data Type Mapping Verification

Java and Python basic types are not fully identical, requiring additional verification:

Protobuf TypeJava TypePython TypeVerify
int64longintLarge number boundaries
doubledoublefloatPrecision loss
bytesByteStringbytesEncoding format
TimestampInstantdatetimeTimezone handling
Decimal (custom)BigDecimalDecimalPrecision and rounding
Python
# Cross-language type compatibility tests
class TestCrossLanguageTypeCompatibility:

    def test_large_int64_roundtrip(self, grpc_client):
        """Verify int64 large numbers roundtrip between Java and Python"""
        large_value = 2**53 - 1  # JavaScript safe integer limit
        response = grpc_client.echo_int64(large_value)
        assert response.value == large_value

    def test_decimal_precision(self, grpc_client):
        """Verify Decimal maintains precision in cross-language transfer"""
        from decimal import Decimal
        value = Decimal("123456789.123456789")
        response = grpc_client.echo_decimal(str(value))
        assert Decimal(response.value) == value

    def test_timestamp_timezone(self, grpc_client):
        """Verify Timestamp timezone consistency in cross-language transfer"""
        from datetime import datetime, timezone
        now = datetime.now(timezone.utc)
        response = grpc_client.echo_timestamp(now)
        # Allow 1 second tolerance (serialization precision)
        assert abs((response.value - now).total_seconds()) < 1.0

#6. Test Data Management

#6.1 Test Data Strategies

coomia-dip tests use three data strategies:

StrategyTest LayerTool
Builder/FactoryUnit testsJava Builder / Python fixture
Seed dataIntegration testsSQL scripts / Flyway migration
Snapshot dataE2E testsDocker volume snapshot

#6.2 Test Data Isolation

The multi-tenant architecture makes test data isolation straightforward -- each test case uses an independent tenant_id:

Python
@pytest.fixture
def isolated_tenant():
    """Create an isolated tenant for each test case"""
    tenant_id = f"test-{uuid.uuid4().hex[:8]}"
    yield tenant_id
    # Cleanup after test (handled automatically by Testcontainers)

#6.3 Sensitive Data Handling

Test data never contains real user data or credentials. The project standard is clear:

Code
No pushing directly to shared files on develop (must use PR)

This rule applies to test data as well -- files containing test credentials must be excluded via .gitignore and never committed to version control.

#7. Test Orchestration in Continuous Integration

#7.1 CI Pipeline Structure

Code
┌──────────────────────────────────────────────────────┐
│                    CI Pipeline                       │
│                                                      │
│  Stage 1: Static Analysis (parallel)                 │
│  ├─ Java: gradle build (compile + unit tests)        │
│  ├─ Python: ruff check + black --check + mypy        │
│  └─ Proto: protoc --lint                             │
│                                                      │
│  Stage 2: Unit Tests (parallel)                      │
│  ├─ Java: gradle test (JUnit 5)                      │
│  └─ Python: pytest -m "not integration"              │
│                                                      │
│  Stage 3: Integration Tests (parallel)               │
│  ├─ Java: gradle integrationTest (Testcontainers)    │
│  ├─ Python: pytest -m integration                    │
│  └─ gRPC: Contract tests                             │
│                                                      │
│  Stage 4: E2E Tests (sequential)                     │
│  ├─ docker compose up -d                             │
│  ├─ pytest tests/scenarios/                          │
│  └─ docker compose down                              │
│                                                      │
│  Stage 5: Quality Gate                               │
│  ├─ Coverage >= 80%                                  │
│  ├─ 0 Critical vulnerabilities                       │
│  └─ No performance regression                        │
└──────────────────────────────────────────────────────┘

#7.2 Tiered Execution Strategy

LayerFrequencyMax DurationFailure Impact
Unit testsEvery commit2 minutesBlocks merge
Integration testsEvery PR10 minutesBlocks merge
E2E testsDaily / post-merge30 minutesTeam notification
Performance testsWeekly / pre-release2 hoursTeam notification

#8. Test Coverage Strategy

#8.1 Coverage Targets

coomia-dip requires >= 80% coverage across all modules, with different emphasis by code type:

Code TypeTargetFocus
Domain logic>= 90%Edge cases, error paths
gRPC Service>= 85%Request validation, error handling
Repository>= 70%Query correctness (integration tests)
Configuration>= 50%Defaults, environment variables
Generated code (proto)Not countedCovered by contract tests

#8.2 Coverage Tools

LanguageToolReport Format
JavaJaCoCoHTML + XML
Pythoncoverage.py + pytest-covHTML + XML

#8.3 Meaningful Coverage

Coverage numbers are not the goal -- meaningful tests are. The following are anti-patterns:

Python
# Anti-pattern: Increases coverage but no assertions
def test_create_object_runs_without_error():
    service.create_object({"name": "test"})
    # No assert at all -- this test is worthless

# Correct: Clear assertions and expectations
def test_create_object_returns_valid_id():
    result = service.create_object({"name": "test"})
    assert result.object_id is not None
    assert len(result.object_id) == 36  # UUID format

#9. Performance Testing

#9.1 Performance Benchmarks

coomia-dip maintains performance benchmarks for critical operations:

OperationP50 BaselineP99 BaselineTool
Create ObjectType< 10ms< 50msJMH (Java)
Query single object< 5ms< 20msJMH (Java)
Derived property computation< 100ms< 500mspytest-benchmark
Rule evaluation< 10ms< 50mspytest-benchmark
SDK end-to-end call< 50ms< 200msk6

#9.2 Performance Regression Detection

Performance tests in the CI pipeline compare against baseline values, triggering alerts if degradation exceeds 10%:

Python
# Performance benchmark test example
def test_rule_evaluation_performance(benchmark):
    evaluator = RuleEvaluator()
    rule = create_complex_rule(conditions=10)
    context = create_test_context()

    result = benchmark(evaluator.evaluate, rule, context)

    # Benchmark assertions
    assert benchmark.stats["mean"] < 0.01  # Mean < 10ms
    assert benchmark.stats["max"] < 0.05   # Max < 50ms

#10. Chaos Testing and Resilience Verification

#10.1 Fault Injection Scenarios

coomia-dip resilience tests simulate the following failure scenarios:

Failure TypeInjection MethodVerification Target
Kafka unavailableStop Kafka containerEvent buffering, retries
gRPC latencyInject network delayTimeout handling, circuit breaking
Database connection exhaustionLimit connection poolGraceful degradation
Memory pressurecgroup memory limitOOM protection
Reasoning & Decision Layer crashStop Intelligence containerComputation routing degradation

#10.2 Resilience Test Example

Python
class TestPlaneDFailure:
    """Verify degradation behavior when Intelligence Layer fails"""

    def test_derived_property_fallback_when_function_unavailable(
        self, platform, docker_compose
    ):
        """When function compute is unavailable, should fall back to SQL"""
        # Create object (normal state)
        supplier = platform.ontology.create_object(
            type_name="Supplier",
            properties={"name": "Test", "delivery_rate": 0.8},
        )
        time.sleep(2)

        # Stop Intelligence Layer
        docker_compose.stop("intelligence-Layer")

        # Query derived property -- should compute via SQL fallback
        score = platform.ontology.get_property(
            "Supplier", supplier.object_id, "risk_score"
        )

        # Degraded computation should still return valid result
        assert score is not None
        assert 0.0 <= float(score) <= 1.0

        # Restore Intelligence Layer
        docker_compose.start("intelligence-Layer")

#11. Test Environment Management

#11.1 Environment Tiers

EnvironmentPurposeDataLifecycle
Local devUnit tests + quick integrationMock / in-memory DBDeveloper managed
CIFull test suiteTestcontainersCreated/destroyed per run
StagingE2E + performance testsAnonymized production dataPersistent
ProductionCanary testsReal data (read-only)Persistent

#11.2 Local Development Test Acceleration

To give developers fast feedback, coomia-dip provides the following acceleration measures:

  1. Incremental testing: Only run tests affected by changes
  2. Parallel execution: Unit tests support parallel execution (pytest -n auto)
  3. Hot reload: Python tests support auto-rerun on file changes (pytest-watch)
  4. Shared Testcontainers: Reuse database containers within the same test session

#12. Quality Gates and Pre-Commit Checks

#12.1 Required Pre-Commit Checks

The project's CLAUDE.md defines a comprehensive quality gate:

LanguageCheck CommandPurpose
Python lintruff checkCode standards
Python formatblack --check && isort --checkFormat consistency
Python typecheckmypyType safety
Python testpytestFunctional correctness
Java buildgradle buildCompilation + tests
Java testgradle testFunctional correctness

#12.2 Git Hook Integration

coomia-dip uses pre-commit hooks to automatically execute checks before commits:

Bash
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: python-quality
        name: Python Quality Gate
        entry: bash -c 'ruff check && black --check . && mypy .'
        language: system
        types: [python]

      - id: java-build
        name: Java Build
        entry: bash -c 'cd control-Layer && gradle build'
        language: system
        types: [java]

#13. Comparison with Palantir Foundry

DimensionPalantir Foundrycoomia-dip
Test frameworkInternal framework (closed source)JUnit 5 + pytest (open source)
Integration testsFoundry Test RuntimeTestcontainers + Docker Compose
Contract testsInternal API compatibility suiteProtobuf compile-time + gRPC runtime
E2E testsFoundry Scenario RunnerScenario-based pytest
Performance testsInternal benchmark suiteJMH + pytest-benchmark + k6
CoverageInternal standards>= 80% (JaCoCo + coverage.py)

#Key Takeaways

  1. A multi-language platform needs a layered testing strategy, not a unified test framework. Java and Python each have mature testing ecosystems (JUnit 5 and pytest), and forcing unification only adds complexity. The key is using gRPC contract tests as the cross-language "glue layer," ensuring Protobuf interfaces remain compatible at both compile time and runtime.

  2. Scenario testing is the most effective means of verifying multi-Layer collaboration correctness. Unit tests guarantee individual function correctness, integration tests guarantee single-Layer internal correctness, but only scenario tests can verify complete business chains like "create object, publish event, compute derived property, cascade update" that span 4 Layers. Investing 5% of testing effort in scenario-based E2E tests can catch 50% of production bugs.

  3. Testing is the last line of defense for code quality -- all forms of concealment are forbidden. Deleting failing tests, empty catch blocks, assertion-free tests -- these anti-patterns may improve pass rates superficially but hide real problems. coomia-dip's technical red lines explicitly prohibit these behaviors. Combined with the 80% coverage threshold and CI quality gates, they form a complete quality assurance system.

Next Article Preview: [S2-15] Architecture Decision Records: Tracking Every Critical Technical Choice with ADRs -- a deep dive into how coomia-dip uses a structured approach to document decisions like "why gRPC over REST" and "why Kafka over RabbitMQ."

Tags: #testing #test-pyramid #unit-test #integration-test #e2e #scenario-testing #grpc-contract #testcontainers #coverage #performance-testing #chaos-testing #coomia-dip