# Testing Standards und Qualitätssicherung --- guideline_type: "project-standards" scope: "testing-standards" audience: ["developers", "ai-assistants"] last_updated: "2025-09-15" dependencies: ["master-guideline.md", "coding-standards.md"] related_files: ["build.gradle.kts", "src/test/**", "testcontainers.properties"] ai_context: "Testing strategies, test pyramid, tools, coverage requirements, and debugging practices" --- ## 🧪 Testing Standards Tests sind ein integraler Bestandteil jedes Features und müssen einen hohen Standard erfüllen. > **🤖 AI-Assistant Hinweis:** > Testing-Prinzipien für das Meldestelle-Projekt: > - **Test-Pyramide:** 80 %+ Unit-Tests, Integrationstests für externe Systeme > - **Testcontainers:** Goldstandard für Infrastruktur-Tests > - **Debug-Logs:** Präfix `[DEBUG_LOG]` für Test-Ausgaben > - **Result-Pattern:** Tests müssen auch Error-Handling validieren ### Test-Pyramide & Werkzeuge #### Unit-Tests (80 %+ Abdeckung) Für Domänen- und Anwendungslogik (JUnit 5, MockK). ```kotlin class MemberServiceTest { private val memberRepository = mockk() private val eventPublisher = mockk() private val memberService = MemberService(memberRepository, eventPublisher) @Test fun `should return Success when member is created successfully`() { // Given val command = CreateMemberCommand( memberId = MemberId.generate(), name = "Max Mustermann", email = "max@example.com" ) every { memberRepository.save(any()) } returns Result.Success(Unit) every { eventPublisher.publish(any()) } returns Result.Success(Unit) // When val result = memberService.createMember(command) // Then assertThat(result).isInstanceOf>() verify { memberRepository.save(any()) } verify { eventPublisher.publish(ofType()) } } @Test fun `should return Failure when repository save fails`() { // Given val command = CreateMemberCommand( memberId = MemberId.generate(), name = "Max Mustermann", email = "max@example.com" ) every { memberRepository.save(any()) } returns Result.Failure(RepositoryError.DATABASE_ERROR) // When val result = memberService.createMember(command) // Then assertThat(result).isInstanceOf>() verify { memberRepository.save(any()) } verify(exactly = 0) { eventPublisher.publish(any()) } } } ``` #### Integrationstests Decken alle Repository-Implementierungen und externen Integrationen ab. ```kotlin @Testcontainers class MemberRepositoryIntegrationTest { @Container private val postgresContainer = PostgreSQLContainer("postgres:16-alpine") .withDatabaseName("testdb") .withUsername("test") .withPassword("test") private lateinit var memberRepository: MemberRepository @BeforeEach fun setup() { val dataSource = HikariDataSource().apply { jdbcUrl = postgresContainer.jdbcUrl username = postgresContainer.username password = postgresContainer.password } // Run migrations Flyway.configure() .dataSource(dataSource) .locations("db/migration") .load() .migrate() memberRepository = PostgresMemberRepository(dataSource) } @Test fun `should save and retrieve member successfully`() { // Given val member = Member( id = MemberId.generate(), name = "Integration Test Member", email = "integration@test.com" ) // When val saveResult = runBlocking { memberRepository.save(member) } val findResult = runBlocking { memberRepository.findById(member.id) } // Then assertThat(saveResult).isInstanceOf>() assertThat(findResult).isInstanceOf>() val retrievedMember = (findResult as Result.Success).value assertThat(retrievedMember?.id).isEqualTo(member.id) assertThat(retrievedMember?.name).isEqualTo(member.name) assertThat(retrievedMember?.email).isEqualTo(member.email) } } ``` #### Testcontainers als Goldstandard Jede Interaktion mit externer Infrastruktur (DB, Cache, Broker) **muss** mit **Testcontainers** getestet werden. ```kotlin @Testcontainers class EventStoreIntegrationTest { companion object { @Container @JvmStatic private val redisContainer = GenericContainer("redis:7-alpine") .withExposedPorts(6379) @Container @JvmStatic private val kafkaContainer = KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) } @Test fun `should store and retrieve events from Redis`() { println("[DEBUG_LOG] Testing Redis event storage") // Given val eventStore = RedisEventStore( redisHost = redisContainer.host, redisPort = redisContainer.getMappedPort(6379) ) val event = MemberCreatedEvent( memberId = MemberId.generate(), name = "Test Member", timestamp = Instant.now() ) // When val storeResult = runBlocking { eventStore.store(event) } val retrieveResult = runBlocking { eventStore.getEvents(event.memberId) } // Then assertThat(storeResult).isInstanceOf>() assertThat(retrieveResult).isInstanceOf>>() val events = (retrieveResult as Result.Success).value assertThat(events).hasSize(1) assertThat(events.first()).isInstanceOf() println("[DEBUG_LOG] Successfully stored and retrieved ${events.size} events") } } ``` ### Debugging in Tests Debug-Ausgaben im Test-Code müssen mit `[DEBUG_LOG]` beginnen, um sie leicht identifizieren und filtern zu können. ```kotlin @Test fun `should handle complex business scenario`() { println("[DEBUG_LOG] Starting complex business scenario test") // Test implementation println("[DEBUG_LOG] Member created with ID: ${member.id}") println("[DEBUG_LOG] Published ${events.size} domain events") println("[DEBUG_LOG] Test completed successfully") } ``` ## 🎯 AI-Assistenten: Testing-Schnellreferenz ### Test-Kategorien und Werkzeuge | Test-Typ | Coverage-Ziel | Werkzeuge | Verwendung | |-------------------|-------------------------|------------------------------|----------------------------| | Unit-Tests | 80%+ | JUnit 5, MockK, AssertJ | Domänen- & Anwendungslogik | | Integrationstests | Alle Repositories | Testcontainers, JUnit 5 | Externe Integrationen | | End-to-End Tests | Kritische User-Journeys | Testcontainers, REST Assured | Vollständige Workflows | ### Testcontainer-Konfiguration #### PostgresQL ```kotlin @Container private val postgresContainer = PostgreSQLContainer("postgres:16-alpine") .withDatabaseName("testdb") .withUsername("test") .withPassword("test") .withInitScript("test-data.sql") ``` #### Redis ```kotlin @Container private val redisContainer = GenericContainer("redis:7-alpine") .withExposedPorts(6379) .withCommand("redis-server", "--appendonly", "yes") ``` #### Kafka ```kotlin @Container private val kafkaContainer = KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) .withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "true") ``` #### Keycloak ```kotlin @Container private val keycloakContainer = KeycloakContainer("quay.io/keycloak/keycloak:26.0.7") .withRealmImportFile("test-realm.json") .withAdminUsername("admin") .withAdminPassword("admin") ``` ### Test-Patterns für Result-Handling ```kotlin // Success-Case testen @Test fun `should return Success when operation succeeds`() { // Given every { dependency.operation() } returns Result.Success(expectedValue) // When val result = serviceUnderTest.performOperation() // Then assertThat(result).isInstanceOf>() assertThat((result as Result.Success).value).isEqualTo(expectedValue) } // Failure-Case testen @Test fun `should return Failure when dependency fails`() { // Given every { dependency.operation() } returns Result.Failure(ExpectedError.SOME_ERROR) // When val result = serviceUnderTest.performOperation() // Then assertThat(result).isInstanceOf>() assertThat((result as Result.Failure).error).isEqualTo(ExpectedError.SOME_ERROR) } ``` ### Mock-Setup für Services ```kotlin class ServiceTest { private val repository = mockk() private val eventPublisher = mockk() private val externalService = mockk() private val serviceUnderTest = Service(repository, eventPublisher, externalService) @BeforeEach fun setup() { clearAllMocks() // Default mocks every { eventPublisher.publish(any()) } returns Result.Success(Unit) } @AfterEach fun cleanup() { confirmVerified(repository, eventPublisher, externalService) } } ``` ### Testdaten-Builder ```kotlin class MemberTestDataBuilder { private var id: MemberId = MemberId.generate() private var name: String = "Test Member" private var email: String = "test@example.com" private var status: MemberStatus = MemberStatus.ACTIVE fun withId(id: MemberId) = apply { this.id = id } fun withName(name: String) = apply { this.name = name } fun withEmail(email: String) = apply { this.email = email } fun withStatus(status: MemberStatus) = apply { this.status = status } fun build() = Member( id = id, name = name, email = email, status = status ) } // Verwendung in Tests @Test fun `should validate member data`() { val member = MemberTestDataBuilder() .withName("Max Mustermann") .withEmail("max@meldestelle.at") .withStatus(MemberStatus.PENDING) .build() // Test implementation } ``` ### Performance-Tests ```kotlin @Test fun `should handle high load efficiently`() { println("[DEBUG_LOG] Starting performance test with 1000 concurrent operations") val operations = (1..1000).map { async { serviceUnderTest.performOperation( TestCommand(id = MemberId.generate()) ) } } val results = runBlocking { operations.awaitAll() } val successCount = results.count { it is Result.Success } val failureCount = results.count { it is Result.Failure } println("[DEBUG_LOG] Performance test completed: $successCount successes, $failureCount failures") assertThat(successCount).isGreaterThan(950) // 95% success rate minimum } ``` --- **Navigation:** - [Master-Guideline](../master-guideline.md) - übergeordnete Projektrichtlinien - [Coding-Standards](./coding-standards.md) - Code-Qualitätsstandards - [Documentation-Standards](./documentation-standards.md) - Dokumentationsrichtlinien - [Architecture-Principles](./architecture-principles.md) - Architektur-Grundsätze