Repository navigation
Compendium chapter 23 testing qa
Navigation: Home > Pages
"Untested code is legacy code. In ThemisDB, comprehensive testing is not optionalβit's architecture."
ZuverlΓ€ssige Datenbanken erfordern rigorose Tests auf mehreren Ebenen: Unit-Tests fΓΌr AQL-Funktionen, Integrationstests fΓΌr Transaktionen, Performance-Tests fΓΌr Sharding, und Chaos-Tests fΓΌr Netzwerkfehler.
Was Sie in diesem Kapitel lernen:
- AQL Unit-Testing mit AQL-Assertions
- Transaktions-Integrationstests
- Performance-Benchmarking
- Chaos Engineering fΓΌr Fehlerszenarien
- Mutation Testing fΓΌr Query-Robustheit
- CI/CD Pipeline-Integration
graph TB
Commit[Code Commit] --> Build[Build & Compile]
Build --> UnitTests[Unit Tests<br/>Functions & Logic]
UnitTests --> IntTests[Integration Tests<br/>Transactions & Data]
IntTests --> E2E[E2E Tests<br/>Full Workflows]
E2E --> QualityGate{Quality Gate<br/>Coverage greater than 80 percent}
QualityGate -->|Pass| Deploy[Deploy to Staging]
QualityGate -->|Fail| Notify[Notify Developers]
style QualityGate fill:#f093fb
style Deploy fill:#43e97b
style Notify fill:#ff6b6b
Abb. 23.0: CI/CD Test-Pipeline
Die Test-Strategie von ThemisDB folgt einer mehrschichtigen Architektur, die von der klassischen Test-Pyramide inspiriert ist. Moderne Datenbanksysteme erfordern dabei nicht nur funktionale Tests, sondern auch Performance-Tests, Chaos Engineering und kontinuierliche QualitΓ€tssicherung in der CI/CD-Pipeline.
graph TB
Commit[Code Commit] --> Build[Build & Compile]
Build --> UnitTests[Unit Tests<br/>Functions & Logic]
UnitTests --> IntTests[Integration Tests<br/>Transactions & Data]
IntTests --> E2E[E2E Tests<br/>Full Workflows]
E2E --> QualityGate{Quality Gate<br/>Coverage > 80%}
QualityGate -->|Pass| Deploy[Deploy to Staging]
QualityGate -->|Fail| Notify[Notify Developers]
style QualityGate fill:#f093fb
style Deploy fill:#43e97b
style Notify fill:#ff6b6b
Abb. 23.0: CI/CD Test-Pipeline: Automatisierte QualitΓ€tssicherung auf mehreren Ebenen
Die Test-Pyramide nach Mike Cohn beschreibt die optimale Verteilung von Tests in einem Softwareprojekt. FΓΌr ThemisDB bedeutet dies konkret:
| Test-Ebene | Anteil | AusfΓΌhrungszeit | HΓ€ufigkeit | Abdeckung |
|---|---|---|---|---|
| Unit Tests | 70% | <2s | Jeder Commit | Funktionen, Logik |
| Integration Tests | 20% | 10-30s | Pre-Commit | APIs, Services |
| E2E Tests | 10% | 2-10min | Nightly/Release | User Workflows |
Wissenschaftlicher Hintergrund: Die Test-Pyramide basiert auf den Erkenntnissen von Beck (2002) zur Test-Driven Development und Freeman & Pryce (2009) zu Growing Object-Oriented Software. Studien zeigen, dass eine 70/20/10-Verteilung die beste Balance zwischen Fehlerabdeckung (>95%), AusfΓΌhrungsgeschwindigkeit und Wartbarkeit bietet (Fowler, 2012).
Die Testphilosophie von ThemisDB basiert auf drei fundamentalen Prinzipien, die sich in modernen Datenbanksystemen bewΓ€hrt haben:
-
Test-First Development: Wie Beck (2002) in "Test-Driven Development" beschreibt, werden Tests vor der Implementierung geschrieben. Dies fΓΌhrt zu besserem Design und hΓΆherer Code Coverage.
-
Isolation und UnabhΓ€ngigkeit: Jeder Test muss unabhΓ€ngig ausfΓΌhrbar sein (Freeman & Pryce, 2009). ThemisDB verwendet Fixtures und Transaktionen zur Isolation.
-
Kontinuierliche Verifikation: Tests laufen automatisch in der CI/CD-Pipeline bei jedem Commit, wie in Kapitel 30 beschrieben.
ThemisDB verfolgt differenzierte Coverage-Ziele basierend auf KritikalitΓ€t und KomplexitΓ€t:
| Komponente | Ziel-Coverage | PrioritΓ€t | Test-Typ |
|---|---|---|---|
| Core Engine (AQL Parser) | >95% | Kritisch | Unit + Integration |
| Transaction Manager | >90% | Kritisch | Unit + Chaos |
| Storage Layer (RocksDB) | >85% | Hoch | Integration + Performance |
| Query Optimizer | >80% | Hoch | Unit + Benchmark |
| REST API Endpoints | >75% | Mittel | Integration + E2E |
| Admin Tools | >70% | Mittel | E2E |
Wissenschaftliche Grundlage: Myers et al. (2011) zeigen in "The Art of Software Testing", dass 80-90% Code Coverage optimal fΓΌr die meisten Systeme ist. HΓΆhere Werte fΓΌhren zu diminishing returns, wΓ€hrend niedrigere Werte kritische Fehler ΓΌbersehen kΓΆnnen.
flowchart TB
subgraph "Test Pyramid"
E2E[E2E Tests<br/>10% - 2-10min<br/>User Workflows]
Integration[Integration Tests<br/>20% - 10-30s<br/>API & Services]
Unit[Unit Tests<br/>70% - milliseconds<br/>Pure Functions]
E2E --> Integration
Integration --> Unit
end
subgraph "Quality Gates"
Coverage[Coverage > 80%]
Performance[Performance < Baseline]
Security[Security Scan Pass]
end
subgraph "Execution Context"
LocalDev[Local Development]
CI[CI Pipeline]
Staging[Staging Environment]
end
Unit --> LocalDev
Integration --> CI
E2E --> Staging
LocalDev --> Coverage
CI --> Performance
Staging --> Security
Coverage --> Deploy{Deploy?}
Performance --> Deploy
Security --> Deploy
Deploy -->|Yes| Production[Production]
Deploy -->|No| Feedback[Developer Feedback]
style E2E fill:#fa5252
style Integration fill:#fab005
style Unit fill:#4dabf7
style Production fill:#51cf66
Abb. 23.1: Erweiterte Test-Pyramide mit Quality Gates und Execution Contexts
Unit Tests (70%): Fokus auf reine Funktionen und Logik ohne externe AbhΓ€ngigkeiten. Beispiele: AQL-Funktionen, Datenvalidierung, mathematische Operationen.
Integration Tests (20%): Testen die Interaktion zwischen Komponenten wie API-Endpoints, Datenbank-Transaktionen und Service-Integration.
E2E Tests (10%): VollstΓ€ndige User Journeys vom Frontend bis zur Datenpersistierung, inklusive Authentifizierung und Authorisierung.
Pytest ist das fΓΌhrende Test-Framework fΓΌr Python-Anwendungen und wird fΓΌr den ThemisDB Python Client eingesetzt. Es bietet Fixtures, Parametrisierung und aussagekrΓ€ftige Fehlermeldungen.
Pytest Test-Suite fΓΌr ThemisDB REST API Client (mit deutschen Kommentaren):
# tests/test_themis_client.py
"""
Unit Tests fΓΌr ThemisDB Python Client
Basierend auf pytest Framework mit Fixtures und Mocking
"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from themis import ThemisClient
from themis.exceptions import ConnectionError, QueryError
# Fixture: Test-Client mit Mock-Backend
@pytest.fixture
def themis_client():
"""
Erstellt einen ThemisDB Client mit gemocktem Backend.
Verwendung: Isolierte Unit-Tests ohne echte Datenbankverbindung.
"""
client = ThemisClient(endpoints=["http://localhost:8529"])
client._session = MagicMock() # Mock HTTP Session
return client
# Fixture: Beispiel-Dokumente fΓΌr Tests
@pytest.fixture
def sample_documents():
"""
Erzeugt konsistente Test-Dokumente fΓΌr alle Tests.
Verwende diese Fixtures statt hartcodierte Werte fΓΌr Wartbarkeit.
"""
return [
{"_key": "user1", "name": "Alice", "age": 30, "role": "admin"},
{"_key": "user2", "name": "Bob", "age": 25, "role": "user"},
{"_key": "user3", "name": "Charlie", "age": 35, "role": "user"}
]
# Test 1: Dokument einfΓΌgen (CREATE)
def test_insert_document(themis_client, sample_documents):
"""
Testet das EinfΓΌgen eines einzelnen Dokuments.
Verifiziert: Korrekte API-Parameter und RΓΌckgabewert.
"""
# Arrange: Mock-Response vorbereiten
mock_response = {"_key": "user1", "_id": "users/user1", "_rev": "_abc123"}
themis_client._request = Mock(return_value=mock_response)
# Act: Dokument einfΓΌgen
result = themis_client.insert("users", sample_documents[0])
# Assert: ΓberprΓΌfe RΓΌckgabewert und API-Aufruf
assert result["_key"] == "user1"
themis_client._request.assert_called_once()
args = themis_client._request.call_args
assert args[0][0] == "POST" # HTTP-Methode
assert "users" in args[0][1] # Collection-Name in URL
# Test 2: Batch-Insert mit Transaktionen
def test_batch_insert_with_transaction(themis_client, sample_documents):
"""
Testet Batch-Insert mit ACID-Garantien.
Wichtig fΓΌr Performance bei groΓen Datenmengen (siehe Kapitel 20).
"""
mock_response = {
"inserted": len(sample_documents),
"errors": 0,
"documents": sample_documents
}
themis_client._request = Mock(return_value=mock_response)
# Batch-Insert in einer Transaktion
result = themis_client.insert_batch("users", sample_documents, transaction=True)
assert result["inserted"] == 3
assert result["errors"] == 0
# Test 3: Fehlerbehandlung bei Connection-Error
def test_connection_error_handling(themis_client):
"""
Testet robuste Fehlerbehandlung bei Netzwerkproblemen.
Resilience Pattern: Siehe Chaos Engineering (Kapitel 23.6).
"""
themis_client._request = Mock(side_effect=ConnectionError("Network unreachable"))
with pytest.raises(ConnectionError) as exc_info:
themis_client.query("FOR doc IN users RETURN doc")
assert "Network unreachable" in str(exc_info.value)
# Test 4: Parametrisierte Tests fΓΌr verschiedene Datentypen
@pytest.mark.parametrize("data,expected_type", [
({"name": "Test"}, dict),
([1, 2, 3], list),
("simple string", str),
(42, int),
])
def test_data_serialization(themis_client, data, expected_type):
"""
Parametrisierter Test fΓΌr verschiedene Datentypen.
Pattern: Teste Edge-Cases mit einer Test-Funktion (Myers, 2011).
"""
import json
serialized = json.dumps(data)
deserialized = json.loads(serialized)
assert isinstance(deserialized, expected_type)
# Test 5: Performance-Benchmark mit pytest-benchmark
def test_query_performance(benchmark, themis_client):
"""
Benchmark-Test fΓΌr Query-Performance.
Threshold: <100ms fΓΌr einfache Queries (siehe Kapitel 23.5).
"""
mock_result = {"data": [{"_key": f"doc{i}"} for i in range(100)]}
themis_client._request = Mock(return_value=mock_result)
# Benchmark fΓΌhrt Funktion mehrfach aus und misst Statistiken
result = benchmark(themis_client.query, "FOR doc IN test RETURN doc")
# Assertions auf Benchmark-Metriken
assert benchmark.stats['mean'] < 0.1 # <100ms durchschnittlichTest Execution und Reporting:
# Alle Tests ausfΓΌhren mit Coverage-Report
pytest tests/ --cov=themis --cov-report=html --cov-report=term
# Nur Benchmark-Tests
pytest tests/test_themis_client.py::test_query_performance --benchmark-only
# Mit verbose Output fΓΌr Debugging
pytest tests/ -v --tb=short
# Parallel Execution fΓΌr schnellere CI-Pipelines
pytest tests/ -n 4 # 4 parallel workersJest ist das Standard-Test-Framework fΓΌr JavaScript/TypeScript und wird fΓΌr den ThemisDB Node.js Client verwendet. Es bietet integriertes Mocking, Snapshot-Testing und parallele AusfΓΌhrung.
Jest Integration Test fΓΌr ThemisDB Node.js Client:
// tests/integration/themis-client.test.js
/**
* Integration Tests fΓΌr ThemisDB Node.js Client
* Testet reale API-Interaktionen mit Test-Container
*
* Setup: docker run -p 8529:8529 themisdb:latest
*/
const { ThemisClient } = require('@themisdb/client');
const { beforeAll, afterAll, describe, test, expect } = require('@jest/globals');
// Test-Suite mit Setup/Teardown
describe('ThemisDB Node.js Client Integration Tests', () => {
let client;
const TEST_COLLECTION = 'test_users_jest';
// Setup: Wird einmal vor allen Tests ausgefΓΌhrt
beforeAll(async () => {
// Client initialisieren
client = new ThemisClient({
endpoints: ['http://localhost:8529'],
auth: { username: 'root', password: 'test' }
});
// Test-Collection erstellen
try {
await client.createCollection(TEST_COLLECTION);
} catch (err) {
// Collection existiert bereits - OK fΓΌr lokale Tests
if (!err.message.includes('duplicate')) throw err;
}
});
// Teardown: Cleanup nach allen Tests
afterAll(async () => {
try {
await client.dropCollection(TEST_COLLECTION);
await client.disconnect();
} catch (err) {
console.error('Cleanup error:', err);
}
});
// Test 1: CRUD Operations
describe('CRUD Operations', () => {
test('sollte Dokument einfΓΌgen und lesen', async () => {
// Arrange
const testDoc = {
name: 'Alice',
email: 'alice@example.com',
age: 30
};
// Act: Insert
const insertResult = await client.insert(TEST_COLLECTION, testDoc);
expect(insertResult._key).toBeDefined();
// Act: Read
const readResult = await client.get(TEST_COLLECTION, insertResult._key);
// Assert: Vergleiche alle Felder
expect(readResult.name).toBe(testDoc.name);
expect(readResult.email).toBe(testDoc.email);
expect(readResult.age).toBe(testDoc.age);
});
test('sollte Dokument aktualisieren', async () => {
// Insert initial document
const doc = await client.insert(TEST_COLLECTION, { name: 'Bob', age: 25 });
// Update
await client.update(TEST_COLLECTION, doc._key, { age: 26 });
// Verify
const updated = await client.get(TEST_COLLECTION, doc._key);
expect(updated.age).toBe(26);
expect(updated.name).toBe('Bob'); // Name unverΓ€ndert
});
});
// Test 2: AQL Query Integration
describe('AQL Query Execution', () => {
test('sollte gefilterte Query ausfΓΌhren', async () => {
// Setup: Test-Daten einfΓΌgen
const users = [
{ name: 'Alice', role: 'admin', active: true },
{ name: 'Bob', role: 'user', active: true },
{ name: 'Charlie', role: 'user', active: false }
];
for (const user of users) {
await client.insert(TEST_COLLECTION, user);
}
// Query: Alle aktiven User
const query = `
FOR user IN ${TEST_COLLECTION}
FILTER user.active == true
SORT user.name ASC
RETURN user
`;
const result = await client.query(query);
// Assertions
expect(result).toHaveLength(2);
expect(result[0].name).toBe('Alice');
expect(result[1].name).toBe('Bob');
});
test('sollte Aggregation mit AQL ausfΓΌhren', async () => {
const query = `
FOR user IN ${TEST_COLLECTION}
COLLECT role = user.role WITH COUNT INTO count
RETURN { role, count }
`;
const result = await client.query(query);
// Erwarte mindestens "admin" und "user" Roles
expect(result.length).toBeGreaterThanOrEqual(2);
const adminRole = result.find(r => r.role === 'admin');
expect(adminRole).toBeDefined();
expect(adminRole.count).toBeGreaterThan(0);
});
});
// Test 3: Error Handling
describe('Error Handling', () => {
test('sollte bei ungΓΌltiger Collection Fehler werfen', async () => {
await expect(
client.get('nonexistent_collection', 'key123')
).rejects.toThrow(/collection not found/i);
});
test('sollte bei ungΓΌltiger Query Fehler werfen', async () => {
const invalidQuery = 'INVALID AQL SYNTAX HERE';
await expect(
client.query(invalidQuery)
).rejects.toThrow(/syntax error/i);
});
});
// Test 4: Transaction Support
describe('Transaction Support', () => {
test('sollte ACID-Transaktion erfolgreich committen', async () => {
const trx = await client.beginTransaction([TEST_COLLECTION]);
try {
// Insert in Transaction
await trx.insert(TEST_COLLECTION, { name: 'TX User 1' });
await trx.insert(TEST_COLLECTION, { name: 'TX User 2' });
// Commit
await trx.commit();
// Verify: Dokumente existieren
const query = `
FOR user IN ${TEST_COLLECTION}
FILTER user.name LIKE 'TX User%'
RETURN user
`;
const result = await client.query(query);
expect(result).toHaveLength(2);
} catch (err) {
await trx.abort();
throw err;
}
});
test('sollte Transaction bei Fehler rollbacken', async () => {
const trx = await client.beginTransaction([TEST_COLLECTION]);
try {
await trx.insert(TEST_COLLECTION, { name: 'Should Rollback' });
// Simuliere Fehler
throw new Error('Simulated transaction error');
} catch (err) {
await trx.abort();
}
// Verify: Dokument existiert NICHT
const query = `
FOR user IN ${TEST_COLLECTION}
FILTER user.name == 'Should Rollback'
RETURN user
`;
const result = await client.query(query);
expect(result).toHaveLength(0);
});
});
});
// Performance-Metriken ausgeben
afterAll(() => {
if (global.performance && global.performance.getEntries) {
const entries = global.performance.getEntries();
console.log(`\nTest Performance Metriken:`);
console.log(`Gesamtzeit: ${entries.reduce((sum, e) => sum + e.duration, 0).toFixed(2)}ms`);
}
});Jest Configuration fΓΌr ThemisDB Tests:
// jest.config.js
module.exports = {
// Test-Umgebung
testEnvironment: 'node',
// Coverage-Konfiguration
collectCoverageFrom: [
'src/**/*.{js,ts}',
'!src/**/*.d.ts',
'!src/**/index.{js,ts}',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
// Test-Matching
testMatch: [
'**/tests/**/*.test.js',
'**/tests/**/*.spec.js',
],
// Setup-Dateien
setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
// Timeouts fΓΌr Integration-Tests
testTimeout: 30000, // 30s fΓΌr DB-Operationen
// Reporter
reporters: [
'default',
['jest-junit', {
outputDirectory: './test-results',
outputName: 'junit.xml',
}],
],
};Mocking-Strategien fΓΌr Datenbank-Tests:
- External Service Mocking: Mock externe APIs und Services (siehe Kapitel 37)
- Database Mocking: Verwende In-Memory-DBs fΓΌr Unit-Tests (z.B. SQLite statt RocksDB)
-
Time Mocking: Mock
Date.now()fΓΌr zeitbasierte Tests (Timeseries)
Fixture-Patterns:
# conftest.py: Shared Fixtures fΓΌr pytest
import pytest
from themis import ThemisClient
@pytest.fixture(scope="session")
def db_client():
"""Session-scoped Client - wird einmal pro Test-Session erstellt"""
client = ThemisClient(endpoints=["http://localhost:8529"])
yield client
client.close()
@pytest.fixture(scope="function")
def clean_database(db_client):
"""Function-scoped Cleanup - lΓ€uft vor jedem Test"""
# Setup
yield
# Teardown: Cleanup nach Test
for collection in ["test_users", "test_orders"]:
try:
db_client.truncate(collection)
except:
passVerzeichnis-Struktur fΓΌr Tests:
tests/
βββ unit/ # Pure Unit Tests (70%)
β βββ test_aql_parser.py
β βββ test_query_optimizer.py
β βββ test_validators.py
βββ integration/ # Integration Tests (20%)
β βββ test_api_endpoints.py
β βββ test_transactions.py
β βββ test_storage_layer.py
βββ e2e/ # End-to-End Tests (10%)
β βββ test_user_workflows.py
β βββ test_admin_operations.py
βββ performance/ # Performance Tests
β βββ test_benchmarks.py
β βββ test_load_scenarios.py
βββ fixtures/ # Shared Test Data
β βββ users.json
β βββ sample_graphs.json
βββ conftest.py # Pytest Configuration
Naming Conventions:
-
Test-Files:
test_*.pyoder*_test.py -
Test-Functions:
test_should_<expected_behavior>_when_<condition>() -
Fixtures: Beschreibende Namen ohne
test_Prefix -
Mocks:
mock_<component>oderfake_<service>
Integration Tests verifizieren die Zusammenarbeit mehrerer Komponenten, wie API-Endpoints, Transaktionen und Storage-Layer. Sie fΓΌllen die kritische LΓΌcke zwischen isolierten Unit-Tests und vollstΓ€ndigen E2E-Tests.
Pact ist ein Framework fΓΌr Consumer-Driven Contract Testing, das sicherstellt, dass API-Producer und -Consumer kompatibel bleiben. Dies ist essentiell fΓΌr Microservice-Architekturen mit ThemisDB als Backend.
Pact Contract Test fΓΌr ThemisDB REST API:
// tests/contract/themis-api.pact.test.js
/**
* Consumer-Driven Contract Test fΓΌr ThemisDB REST API
* Verifiziert API-KompatibilitΓ€t zwischen Client und Server
*
* Pattern: Consumer definiert erwarteten Contract, Provider muss ihn erfΓΌllen
* Siehe: "Building Microservices" (Newman, 2021)
*/
const { Pact } = require('@pact-foundation/pact');
const { ThemisClient } = require('@themisdb/client');
const path = require('path');
describe('ThemisDB API Contract Tests', () => {
// Pact Mock-Server konfigurieren
const provider = new Pact({
consumer: 'ThemisDB-NodeClient',
provider: 'ThemisDB-RestAPI',
port: 8530,
log: path.resolve(process.cwd(), 'logs', 'pact.log'),
dir: path.resolve(process.cwd(), 'pacts'),
logLevel: 'INFO',
});
// Setup: Mock-Server starten
beforeAll(() => provider.setup());
afterEach(() => provider.verify());
afterAll(() => provider.finalize());
describe('GET /api/collection/:name/:key', () => {
test('sollte einzelnes Dokument zurΓΌckgeben', async () => {
// Contract definieren: Erwartete Request/Response
await provider.addInteraction({
state: 'Dokument user123 existiert in users collection',
uponReceiving: 'eine Anfrage fΓΌr user123',
withRequest: {
method: 'GET',
path: '/api/collection/users/user123',
headers: {
'Authorization': 'Bearer test-token',
'Accept': 'application/json',
},
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: {
_key: 'user123',
_id: 'users/user123',
_rev: '_abc123',
name: 'Alice Smith',
email: 'alice@example.com',
age: 30,
},
},
});
// Test gegen Mock-Server
const client = new ThemisClient({
endpoints: [`http://localhost:8530`],
auth: { token: 'test-token' }
});
const result = await client.get('users', 'user123');
// Assertions
expect(result._key).toBe('user123');
expect(result.name).toBe('Alice Smith');
expect(result.email).toBe('alice@example.com');
});
});
describe('POST /api/collection/:name', () => {
test('sollte neues Dokument einfΓΌgen', async () => {
const newUser = {
name: 'Bob Jones',
email: 'bob@example.com',
age: 25,
};
await provider.addInteraction({
state: 'users collection existiert',
uponReceiving: 'eine Anfrage zum EinfΓΌgen eines Users',
withRequest: {
method: 'POST',
path: '/api/collection/users',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer test-token',
},
body: newUser,
},
willRespondWith: {
status: 201,
headers: {
'Content-Type': 'application/json',
},
body: {
_key: Pact.Matchers.like('user456'),
_id: Pact.Matchers.like('users/user456'),
_rev: Pact.Matchers.like('_xyz789'),
...newUser,
},
},
});
const client = new ThemisClient({
endpoints: [`http://localhost:8530`],
auth: { token: 'test-token' }
});
const result = await client.insert('users', newUser);
expect(result._key).toBeDefined();
expect(result.name).toBe(newUser.name);
});
});
describe('POST /api/query', () => {
test('sollte AQL Query ausfΓΌhren', async () => {
const query = 'FOR user IN users FILTER user.age > 25 RETURN user';
await provider.addInteraction({
state: 'users collection enthΓ€lt mehrere Dokumente',
uponReceiving: 'eine AQL Query-Anfrage',
withRequest: {
method: 'POST',
path: '/api/query',
headers: {
'Content-Type': 'application/json',
},
body: {
query: query,
bindVars: {},
},
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: {
result: Pact.Matchers.eachLike({
_key: Pact.Matchers.like('user123'),
name: Pact.Matchers.like('Alice'),
age: Pact.Matchers.integer(30),
}),
hasMore: false,
cached: false,
extra: {
stats: {
writesExecuted: 0,
writesIgnored: 0,
scannedFull: 0,
scannedIndex: 0,
},
},
},
},
});
const client = new ThemisClient({
endpoints: [`http://localhost:8530`]
});
const result = await client.query(query);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
expect(result[0]._key).toBeDefined();
});
});
});Database Integration Tests verifizieren die korrekte Interaktion mit dem Storage-Layer, Transaktionen und Indizes.
Integration Test fΓΌr ThemisDB Transaktionen:
# tests/integration/test_transactions.py
"""
Integration Tests fΓΌr ThemisDB ACID-Transaktionen
Testet Isolation Levels, Rollback-Mechanismen und Deadlock-Detection
"""
import pytest
from themis import ThemisClient, TransactionAbortedError
import threading
import time
@pytest.fixture(scope="module")
def db():
"""Module-scoped Database Client"""
client = ThemisClient(endpoints=["http://localhost:8529"])
# Setup: Test-Collections erstellen
client.create_collection("test_accounts")
client.create_collection("test_transactions")
yield client
# Teardown
client.drop_collection("test_accounts")
client.drop_collection("test_transactions")
client.close()
def test_transaction_commit_success(db):
"""
Test: Erfolgreiche Transaktion mit COMMIT
Pattern: ACID Atomicity - Alle Operationen oder keine
"""
# Setup: Initial Account Balance
acc1_key = db.insert("test_accounts", {"owner": "alice", "balance": 1000})["_key"]
acc2_key = db.insert("test_accounts", {"owner": "bob", "balance": 500})["_key"]
# Transaction: Transfer 200 von Alice zu Bob
trx = db.begin_transaction(["test_accounts"])
try:
# Debit von Alice
acc1 = trx.get("test_accounts", acc1_key)
trx.update("test_accounts", acc1_key, {"balance": acc1["balance"] - 200})
# Credit zu Bob
acc2 = trx.get("test_accounts", acc2_key)
trx.update("test_accounts", acc2_key, {"balance": acc2["balance"] + 200})
# Commit Transaction
trx.commit()
# Verify: Balances korrekt
acc1_final = db.get("test_accounts", acc1_key)
acc2_final = db.get("test_accounts", acc2_key)
assert acc1_final["balance"] == 800, "Alice Balance sollte 800 sein"
assert acc2_final["balance"] == 700, "Bob Balance sollte 700 sein"
except Exception as e:
trx.abort()
raise
def test_transaction_rollback_on_error(db):
"""
Test: Automatischer Rollback bei Fehler
Pattern: ACID Atomicity - Fehler fΓΌhrt zu vollstΓ€ndigem Rollback
"""
# Setup
acc_key = db.insert("test_accounts", {"owner": "charlie", "balance": 1000})["_key"]
# Transaction mit simuliertem Fehler
trx = db.begin_transaction(["test_accounts"])
try:
# Erfolgreiche Operation
acc = trx.get("test_accounts", acc_key)
trx.update("test_accounts", acc_key, {"balance": acc["balance"] - 500})
# Simuliere Fehler (z.B. Constraint Violation)
raise ValueError("Simulated error - insufficient funds check")
except ValueError:
# Rollback bei Fehler
trx.abort()
# Verify: Balance unverΓ€ndert
acc_final = db.get("test_accounts", acc_key)
assert acc_final["balance"] == 1000, "Balance sollte unverΓ€ndert sein nach Rollback"
def test_transaction_isolation_read_committed(db):
"""
Test: READ COMMITTED Isolation Level
Pattern: Keine Dirty Reads - Lese nur committete Daten
Siehe: "Database Systems" (Elmasri & Navathe, 2015)
"""
# Setup
acc_key = db.insert("test_accounts", {"owner": "dave", "balance": 1000})["_key"]
# Thread 1: Liest Account in Transaction
read_value = []
def reader_thread():
time.sleep(0.1) # Warte bis Writer begonnen hat
trx = db.begin_transaction(["test_accounts"], isolation="read_committed")
acc = trx.get("test_accounts", acc_key)
read_value.append(acc["balance"])
trx.commit()
# Thread 2: Schreibt Account in Transaction (nicht committed)
def writer_thread():
trx = db.begin_transaction(["test_accounts"])
acc = trx.get("test_accounts", acc_key)
trx.update("test_accounts", acc_key, {"balance": 500})
time.sleep(0.5) # Halte Transaction offen
trx.commit()
# Start Threads
t1 = threading.Thread(target=reader_thread)
t2 = threading.Thread(target=writer_thread)
t2.start()
t1.start()
t1.join()
t2.join()
# Verify: Reader sah entweder alten (1000) oder neuen (500) Wert,
# aber NICHT einen uncommitted intermediate Wert
assert read_value[0] in [1000, 500], "Kein Dirty Read"
def test_transaction_deadlock_detection(db):
"""
Test: Automatische Deadlock-Detection und Abort
Pattern: Erkenne zyklische Wait-AbhΓ€ngigkeiten und breche ab
"""
# Setup: Zwei Accounts
acc1_key = db.insert("test_accounts", {"owner": "eve", "balance": 1000})["_key"]
acc2_key = db.insert("test_accounts", {"owner": "frank", "balance": 1000})["_key"]
deadlock_detected = []
# Thread 1: Locked acc1, will acc2 locken
def thread1():
try:
trx = db.begin_transaction(["test_accounts"])
trx.update("test_accounts", acc1_key, {"balance": 900})
time.sleep(0.2)
trx.update("test_accounts", acc2_key, {"balance": 1100})
trx.commit()
except TransactionAbortedError as e:
if "deadlock" in str(e).lower():
deadlock_detected.append(True)
# Thread 2: Locked acc2, will acc1 locken
def thread2():
try:
trx = db.begin_transaction(["test_accounts"])
trx.update("test_accounts", acc2_key, {"balance": 900})
time.sleep(0.2)
trx.update("test_accounts", acc1_key, {"balance": 1100})
trx.commit()
except TransactionAbortedError as e:
if "deadlock" in str(e).lower():
deadlock_detected.append(True)
t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)
t1.start()
t2.start()
t1.join()
t2.join()
# Verify: Mindestens eine Transaction wurde wegen Deadlock aborted
assert len(deadlock_detected) > 0, "Deadlock sollte erkannt werden"
@pytest.mark.benchmark
def test_transaction_throughput(db, benchmark):
"""
Benchmark: Transaktions-Throughput
Metrik: Transaktionen pro Sekunde
"""
def run_transaction():
trx = db.begin_transaction(["test_accounts"])
try:
acc = trx.insert("test_accounts", {"owner": "bench", "balance": 100})
trx.commit()
except:
trx.abort()
result = benchmark(run_transaction)
# Assertion: Mindestens 100 TPS (Transaktionen pro Sekunde)
# Siehe Performance-Benchmarks in Kapitel 23.5
assert result.stats["ops"] > 100, "TPS sollte > 100 sein"Service Interaction Tests verifizieren die Kommunikation zwischen ThemisDB und externen Services wie Monitoring, Authentication und Message Queues.
Integration Test fΓΌr Prometheus Metrics Endpoint:
# tests/integration/test_prometheus_metrics.py
"""
Integration Test fΓΌr Prometheus Metrics Export
Verifiziert, dass ThemisDB korrekte Metriken im Prometheus-Format exportiert
"""
import pytest
import requests
from prometheus_client.parser import text_string_to_metric_families
def test_prometheus_metrics_endpoint():
"""
Test: Prometheus /metrics Endpoint
Siehe: Monitoring-Integration in Kapitel 19
"""
# Request zu ThemisDB Metrics Endpoint
response = requests.get("http://localhost:8529/_admin/metrics")
assert response.status_code == 200
assert response.headers["Content-Type"].startswith("text/plain")
# Parse Prometheus-Format
metrics = {}
for family in text_string_to_metric_families(response.text):
metrics[family.name] = family
# Verify: Wichtige Metriken vorhanden
assert "themisdb_queries_total" in metrics, "Query Counter fehlt"
assert "themisdb_query_duration_seconds" in metrics, "Query Duration fehlt"
assert "themisdb_connections_active" in metrics, "Active Connections fehlt"
assert "themisdb_storage_bytes_used" in metrics, "Storage Metrics fehlt"
# Verify: Metric Types korrekt
assert metrics["themisdb_queries_total"].type == "counter"
assert metrics["themisdb_query_duration_seconds"].type == "histogram"
assert metrics["themisdb_connections_active"].type == "gauge"Playwright ist ein modernes Framework fΓΌr browserbasierte E2E-Tests. Es unterstΓΌtzt Chromium, Firefox und WebKit mit einer einheitlichen API.
Playwright E2E Test fΓΌr ThemisDB Web UI:
Playwright E2E Test fΓΌr ThemisDB Web UI:
// tests/e2e/user-journey.spec.ts
/**
* End-to-End Tests fΓΌr ThemisDB Web UI
* Testet vollstΓ€ndige User Journeys vom Login bis zur Datenmanipulation
*
* Framework: Playwright
* Pattern: Page Object Model fΓΌr Wartbarkeit
*/
import { test, expect, Page } from '@playwright/test';
// Page Object: Login Page
class LoginPage {
constructor(private page: Page) {}
async navigate() {
await this.page.goto('http://localhost:8529/_db/_system/_admin/aardvark/index.html');
}
async login(username: string, password: string) {
// Eingabe Credentials
await this.page.fill('input[name="username"]', username);
await this.page.fill('input[name="password"]', password);
// Submit Form
await this.page.click('button[type="submit"]');
// Warte auf Navigation nach Login
await this.page.waitForURL('**/dashboard');
}
}
// Page Object: Collections Page
class CollectionsPage {
constructor(private page: Page) {}
async navigate() {
await this.page.click('a[href*="/collections"]');
await this.page.waitForLoadState('networkidle');
}
async createCollection(name: string) {
// Klicke "New Collection" Button
await this.page.click('button:has-text("New Collection")');
// FΓΌlle Dialog aus
await this.page.fill('input[name="collectionName"]', name);
await this.page.click('button:has-text("Create")');
// Warte auf Success-Notification
await expect(
this.page.locator('.notification.success')
).toBeVisible({ timeout: 5000 });
}
async openCollection(name: string) {
await this.page.click(`tr:has-text("${name}") a.collection-link`);
await this.page.waitForLoadState('networkidle');
}
async insertDocument(doc: object) {
// Γffne "New Document" Dialog
await this.page.click('button:has-text("New Document")');
// FΓΌlle JSON Editor
const editor = this.page.locator('.ace_editor');
await editor.click();
await this.page.keyboard.type(JSON.stringify(doc, null, 2));
// Save
await this.page.click('button:has-text("Save")');
// Verify Success
await expect(
this.page.locator('.notification.success')
).toContainText('Document created');
}
}
// Page Object: Query Editor Page
class QueryEditorPage {
constructor(private page: Page) {}
async navigate() {
await this.page.click('a[href*="/queries"]');
await this.page.waitForLoadState('networkidle');
}
async executeQuery(aql: string): Promise<any[]> {
// Klicke in Query Editor
const editor = this.page.locator('.ace_editor');
await editor.click();
// Clear existing query
await this.page.keyboard.press('Control+A');
await this.page.keyboard.press('Delete');
// Neue Query eingeben
await this.page.keyboard.type(aql);
// Execute Query
await this.page.click('button:has-text("Execute")');
// Warte auf Results
await this.page.waitForSelector('.query-results', { timeout: 10000 });
// Parse Results aus UI Table
const rows = await this.page.locator('.query-results tbody tr').all();
const results = [];
for (const row of rows) {
const cells = await row.locator('td').all();
const rowData = {};
for (const cell of cells) {
const text = await cell.textContent();
if (text) results.push(text.trim());
}
}
return results;
}
}
// E2E Test Suite
test.describe('ThemisDB Web UI - User Journey', () => {
let loginPage: LoginPage;
let collectionsPage: CollectionsPage;
let queryPage: QueryEditorPage;
test.beforeEach(async ({ page }) => {
// Initialize Page Objects
loginPage = new LoginPage(page);
collectionsPage = new CollectionsPage(page);
queryPage = new QueryEditorPage(page);
// Common Setup: Login
await loginPage.navigate();
await loginPage.login('root', 'test');
});
test('sollte Collection erstellen und Dokument einfΓΌgen', async ({ page }) => {
// Navigiere zu Collections
await collectionsPage.navigate();
// Erstelle neue Collection
const collectionName = `test_collection_${Date.now()}`;
await collectionsPage.createCollection(collectionName);
// Verify: Collection in Liste
await expect(page.locator(`tr:has-text("${collectionName}")`)).toBeVisible();
// Γffne Collection
await collectionsPage.openCollection(collectionName);
// Insert Document
const testDoc = {
name: 'Alice',
email: 'alice@example.com',
age: 30
};
await collectionsPage.insertDocument(testDoc);
// Verify: Dokument in Collection sichtbar
await expect(page.locator(`td:has-text("${testDoc.name}")`)).toBeVisible();
});
test('sollte AQL Query ausfΓΌhren und Ergebnisse anzeigen', async ({ page }) => {
// Setup: Collection mit Test-Daten (via API fΓΌr schnelleres Setup)
const collectionName = `test_users_${Date.now()}`;
await page.request.post('http://localhost:8529/_api/collection', {
data: { name: collectionName }
});
// Insert Test Documents via API
const testUsers = [
{ name: 'Alice', age: 30, city: 'Berlin' },
{ name: 'Bob', age: 25, city: 'Munich' },
{ name: 'Charlie', age: 35, city: 'Hamburg' }
];
for (const user of testUsers) {
await page.request.post(`http://localhost:8529/_api/document/${collectionName}`, {
data: user
});
}
// Navigiere zu Query Editor
await queryPage.navigate();
// Execute Query
const query = `
FOR user IN ${collectionName}
FILTER user.age > 25
SORT user.name ASC
RETURN user
`;
const results = await queryPage.executeQuery(query);
// Verify Results
expect(results.length).toBeGreaterThan(0);
// Verify Result anzeigt gefilterte User
await expect(page.locator('text=/Alice|Charlie/')).toBeVisible();
await expect(page.locator('text=Bob')).not.toBeVisible();
});
test('sollte Graph Visualization anzeigen', async ({ page }) => {
// Navigiere zu Graph Viewer
await page.click('a[href*="/graph"]');
await page.waitForLoadState('networkidle');
// Select Graph
await page.selectOption('select[name="graphName"]', 'social_graph');
// Verify: Graph Canvas sichtbar
await expect(page.locator('canvas.graph-canvas')).toBeVisible();
// Verify: Nodes werden gerendert
const canvasContent = await page.locator('.graph-stats').textContent();
expect(canvasContent).toMatch(/\d+ nodes/i);
});
test('sollte Fehler bei ungΓΌltiger Query anzeigen', async ({ page }) => {
await queryPage.navigate();
// Execute ungΓΌltige Query
const invalidQuery = 'INVALID AQL SYNTAX HERE';
await queryPage.executeQuery(invalidQuery);
// Verify: Error Message wird angezeigt
await expect(
page.locator('.notification.error, .error-message')
).toContainText(/syntax error|parse error/i, { timeout: 5000 });
});
});
// Performance Test: Page Load Times
test.describe('ThemisDB Web UI - Performance', () => {
test('Dashboard sollte in <3s laden', async ({ page }) => {
const startTime = Date.now();
await page.goto('http://localhost:8529/_admin');
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;
// Assertion: Unter 3 Sekunden
expect(loadTime).toBeLessThan(3000);
console.log(`Dashboard load time: ${loadTime}ms`);
});
test('GroΓe Collection (10k docs) sollte performant laden', async ({ page }) => {
// TODO: Setup Collection mit 10k docs via API
await page.goto('http://localhost:8529/_admin/#collections/large_collection');
const startTime = Date.now();
await page.waitForSelector('.document-list', { timeout: 10000 });
const renderTime = Date.now() - startTime;
// Assertion: Rendering unter 5 Sekunden
expect(renderTime).toBeLessThan(5000);
});
});Playwright Configuration:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
// Timeout pro Test
timeout: 30 * 1000,
// Parallele Execution
fullyParallel: true,
workers: process.env.CI ? 1 : undefined,
// Reporter
reporter: [
['html', { outputFolder: 'test-results/playwright-report' }],
['junit', { outputFile: 'test-results/junit.xml' }],
['list'],
],
use: {
// Base URL
baseURL: 'http://localhost:8529',
// Trace bei Fehler
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
// Browser Context Options
viewport: { width: 1280, height: 720 },
ignoreHTTPSErrors: true,
},
// Projekte fΓΌr verschiedene Browser
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
// Web Server (optional: Start ThemisDB automatisch)
webServer: {
command: 'docker run -p 8529:8529 themisdb:latest',
port: 8529,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
});Test Data Factories fΓΌr reproduzierbare Tests:
# tests/fixtures/factories.py
"""
Test Data Factories fΓΌr ThemisDB E2E Tests
Pattern: Builder Pattern fΓΌr konsistente Test-Daten
"""
from typing import Dict, List
import random
from datetime import datetime, timedelta
class UserFactory:
"""Factory fΓΌr Test-User-Dokumente"""
@staticmethod
def create(name: str = None, **kwargs) -> Dict:
"""Erstelle User mit Default-Werten"""
return {
"name": name or f"User_{random.randint(1000, 9999)}",
"email": kwargs.get("email", f"user{random.randint(1000,9999)}@example.com"),
"age": kwargs.get("age", random.randint(18, 80)),
"role": kwargs.get("role", "user"),
"created_at": kwargs.get("created_at", datetime.utcnow().isoformat()),
"active": kwargs.get("active", True),
}
@staticmethod
def create_batch(count: int, **kwargs) -> List[Dict]:
"""Erstelle mehrere User"""
return [UserFactory.create(**kwargs) for _ in range(count)]
@staticmethod
def create_admin() -> Dict:
"""Erstelle Admin-User"""
return UserFactory.create(role="admin", name="Admin User")
class OrderFactory:
"""Factory fΓΌr Order-Dokumente"""
@staticmethod
def create(user_id: str, **kwargs) -> Dict:
return {
"user_id": user_id,
"order_number": f"ORD-{random.randint(10000, 99999)}",
"items": kwargs.get("items", []),
"total": kwargs.get("total", random.uniform(10.0, 1000.0)),
"status": kwargs.get("status", "pending"),
"created_at": datetime.utcnow().isoformat(),
}
class GraphFactory:
"""Factory fΓΌr Graph-Strukturen"""
@staticmethod
def create_social_network(num_users: int = 10) -> Dict:
"""Erstelle Social Network Graph"""
users = UserFactory.create_batch(num_users)
# ZufΓ€llige Edges (Freundschaften)
edges = []
for i in range(len(users)):
# Jeder User hat 2-5 Freunde
num_friends = random.randint(2, min(5, num_users - 1))
friends = random.sample(range(len(users)), num_friends)
for friend_idx in friends:
if friend_idx != i:
edges.append({
"_from": f"users/{users[i]['name']}",
"_to": f"users/{users[friend_idx]['name']}",
"type": "friend",
"since": (datetime.utcnow() - timedelta(days=random.randint(1, 365))).isoformat()
})
return {
"vertices": users,
"edges": edges
}Locust ist ein Python-basiertes Load-Testing-Framework, das realistische User-Behavior simuliert. Ideal fΓΌr Performance-Testing von ThemisDB unter Last.
Locust Load Test fΓΌr ThemisDB API:
# tests/performance/locustfile.py
"""
Locust Load Test fΓΌr ThemisDB REST API
Simuliert realistische Last mit verschiedenen User-Behaviors
AusfΓΌhrung:
locust -f locustfile.py --host=http://localhost:8529
Dashboard: http://localhost:8089
"""
from locust import HttpUser, task, between, SequentialTaskSet
import random
import json
class ThemisDBUserBehavior(SequentialTaskSet):
"""
Sequentielle User Journey: Login β Browse β Query β Logout
Pattern: Realistisches User-Verhalten modellieren
"""
def on_start(self):
"""Setup: Wird einmal pro User ausgefΓΌhrt"""
self.collection = "test_users"
self.user_keys = []
@task(1)
def login(self):
"""Schritt 1: Authentifizierung"""
response = self.client.post("/_api/auth/login", json={
"username": "testuser",
"password": "testpass"
})
if response.status_code == 200:
self.user.token = response.json()["token"]
@task(5)
def create_document(self):
"""Schritt 2: Dokument erstellen (hΓ€ufigste Operation)"""
doc = {
"name": f"User_{random.randint(1000, 9999)}",
"email": f"user{random.randint(1000,9999)}@example.com",
"age": random.randint(18, 80),
"created_at": "2024-01-01T00:00:00Z"
}
with self.client.post(
f"/_api/document/{self.collection}",
json=doc,
headers={"Authorization": f"Bearer {getattr(self.user, 'token', '')}"},
catch_response=True
) as response:
if response.status_code == 201:
data = response.json()
self.user_keys.append(data["_key"])
response.success()
else:
response.failure(f"Insert failed: {response.status_code}")
@task(10)
def read_document(self):
"""Schritt 3: Dokument lesen (hΓ€ufigste Read-Operation)"""
if not self.user_keys:
return
key = random.choice(self.user_keys)
with self.client.get(
f"/_api/document/{self.collection}/{key}",
headers={"Authorization": f"Bearer {getattr(self.user, 'token', '')}"},
catch_response=True
) as response:
if response.status_code == 200:
response.success()
elif response.status_code == 404:
# Document wurde gelΓΆscht - OK
self.user_keys.remove(key)
response.success()
else:
response.failure(f"Read failed: {response.status_code}")
@task(3)
def execute_query(self):
"""Schritt 4: AQL Query (mittel-hΓ€ufig)"""
query = f"""
FOR doc IN {self.collection}
FILTER doc.age > @minAge
SORT doc.name ASC
LIMIT 10
RETURN doc
"""
with self.client.post(
"/_api/cursor",
json={
"query": query,
"bindVars": {"minAge": random.randint(18, 60)}
},
headers={"Authorization": f"Bearer {getattr(self.user, 'token', '')}"},
catch_response=True
) as response:
if response.status_code == 201:
result = response.json()
# Name mit Query Stats fΓΌr Monitoring
response.success()
# Metrics loggen
stats = result.get("extra", {}).get("stats", {})
print(f"Query Stats: scanned={stats.get('scannedFull', 0)}, "
f"time={stats.get('executionTime', 0)}ms")
else:
response.failure(f"Query failed: {response.status_code}")
@task(2)
def update_document(self):
"""Schritt 5: Dokument aktualisieren (selten)"""
if not self.user_keys:
return
key = random.choice(self.user_keys)
update = {"age": random.randint(18, 80)}
with self.client.patch(
f"/_api/document/{self.collection}/{key}",
json=update,
headers={"Authorization": f"Bearer {getattr(self.user, 'token', '')}"},
catch_response=True
) as response:
if response.status_code in [200, 201, 202]:
response.success()
else:
response.failure(f"Update failed: {response.status_code}")
@task(1)
def delete_document(self):
"""Schritt 6: Dokument lΓΆschen (selten)"""
if not self.user_keys:
return
key = random.choice(self.user_keys)
with self.client.delete(
f"/_api/document/{self.collection}/{key}",
headers={"Authorization": f"Bearer {getattr(self.user, 'token', '')}"},
catch_response=True
) as response:
if response.status_code in [200, 202]:
self.user_keys.remove(key)
response.success()
else:
response.failure(f"Delete failed: {response.status_code}")
class ThemisDBLoadTestUser(HttpUser):
"""
Load Test User mit realistischem Timing
Pattern: Think-Time zwischen Requests simulieren
"""
# Wait zwischen Tasks: 1-5 Sekunden (realistisches User-Verhalten)
wait_time = between(1, 5)
# Task Set
tasks = [ThemisDBUserBehavior]
# User-Weight (fΓΌr verschiedene User-Typen)
weight = 1
class PowerUser(HttpUser):
"""Heavy User mit mehr Last"""
wait_time = between(0.5, 2)
tasks = [ThemisDBUserBehavior]
weight = 2 # Doppelt so viele Power Users
# Custom Event Hooks fΓΌr erweiterte Metriken
from locust import events
@events.test_start.add_listener
def on_test_start(environment, **kwargs):
print("Load Test gestartet!")
print(f"Target Host: {environment.host}")
@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):
print("\nLoad Test beendet!")
print(f"Total Requests: {environment.stats.total.num_requests}")
print(f"Total Failures: {environment.stats.total.num_failures}")
print(f"Average Response Time: {environment.stats.total.avg_response_time:.2f}ms")
print(f"Requests/sec: {environment.stats.total.total_rps:.2f}")k6 ist ein modernes Load-Testing-Tool mit JavaScript-API, ideal fΓΌr Stress-Tests und Spike-Testing.
k6 Stress Test Script:
k6 Stress Test Script:
// tests/performance/stress-test.js
/**
* k6 Stress Test fΓΌr ThemisDB
* Testet System-Verhalten unter extremer Last
*
* Stages:
* 1. Ramp-up: Langsam Last erhΓΆhen
* 2. Spike: PlΓΆtzlicher Last-Anstieg
* 3. Sustained Load: Konstante hohe Last
* 4. Ramp-down: Langsam Last reduzieren
*
* AusfΓΌhrung: k6 run stress-test.js
*/
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
// Custom Metrics
const errorRate = new Rate('errors');
const queryDuration = new Trend('query_duration');
// Test Configuration
export const options = {
stages: [
// Stage 1: Ramp-up (5 min)
{ duration: '5m', target: 50 }, // 50 VUs
// Stage 2: Sustained Load (10 min)
{ duration: '10m', target: 50 },
// Stage 3: Spike Test (2 min)
{ duration: '2m', target: 200 }, // PlΓΆtzlich 200 VUs
// Stage 4: High Load (5 min)
{ duration: '5m', target: 200 },
// Stage 5: Ramp-down (3 min)
{ duration: '3m', target: 0 },
],
thresholds: {
// Performance-Anforderungen (siehe Kapitel 20)
'http_req_duration': ['p(95)<500'], // 95% unter 500ms
'http_req_failed': ['rate<0.01'], // <1% Fehlerrate
'errors': ['rate<0.05'], // <5% Application Errors
},
};
// Setup: Wird einmal vor Tests ausgefΓΌhrt
export function setup() {
const res = http.post('http://localhost:8529/_api/collection', JSON.stringify({
name: 'k6_test_collection'
}), {
headers: { 'Content-Type': 'application/json' },
});
return { collectionName: 'k6_test_collection' };
}
// Main Test Function
export default function(data) {
const baseUrl = 'http://localhost:8529';
const collection = data.collectionName;
// Test 1: Insert Document (30% der Requests)
if (Math.random() < 0.3) {
const doc = {
name: `User_${__VU}_${__ITER}`, // VU = Virtual User, ITER = Iteration
timestamp: new Date().toISOString(),
data: Math.random()
};
const insertRes = http.post(
`${baseUrl}/_api/document/${collection}`,
JSON.stringify(doc),
{ headers: { 'Content-Type': 'application/json' } }
);
check(insertRes, {
'insert status 201': (r) => r.status === 201,
'insert has _key': (r) => JSON.parse(r.body)._key !== undefined,
}) || errorRate.add(1);
}
// Test 2: Query Documents (50% der Requests)
if (Math.random() < 0.5) {
const query = `
FOR doc IN ${collection}
FILTER doc.data > 0.5
LIMIT 100
RETURN doc
`;
const startTime = Date.now();
const queryRes = http.post(
`${baseUrl}/_api/cursor`,
JSON.stringify({ query }),
{ headers: { 'Content-Type': 'application/json' } }
);
const duration = Date.now() - startTime;
queryDuration.add(duration);
check(queryRes, {
'query status 201': (r) => r.status === 201,
'query has result': (r) => JSON.parse(r.body).result !== undefined,
'query time <500ms': (r) => duration < 500,
}) || errorRate.add(1);
}
// Test 3: Complex Aggregation (20% der Requests)
if (Math.random() < 0.2) {
const aggQuery = `
FOR doc IN ${collection}
COLLECT bucket = FLOOR(doc.data * 10) WITH COUNT INTO count
RETURN { bucket, count }
`;
const aggRes = http.post(
`${baseUrl}/_api/cursor`,
JSON.stringify({ query: aggQuery }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(aggRes, {
'aggregation status 201': (r) => r.status === 201,
}) || errorRate.add(1);
}
// Realistische Pause zwischen Requests
sleep(Math.random() * 2); // 0-2 Sekunden
}
// Teardown: Wird einmal nach Tests ausgefΓΌhrt
export function teardown(data) {
const res = http.del(`http://localhost:8529/_api/collection/${data.collectionName}`);
console.log(`Cleanup: Collection deleted (${res.status})`);
}
// Result Summary Handler
export function handleSummary(data) {
return {
'stdout': textSummary(data, { indent: ' ', enableColors: true }),
'summary.json': JSON.stringify(data),
'summary.html': htmlReport(data),
};
}
function textSummary(data, options) {
const { metrics } = data;
return `
=== k6 Stress Test Results ===
Duration: ${data.state.testRunDurationMs / 1000}s
VUs Max: ${data.metrics.vus_max.values.max}
HTTP Metrics:
- Requests: ${metrics.http_reqs.values.count}
- Failed: ${metrics.http_req_failed.values.rate * 100}%
- Duration p95: ${metrics.http_req_duration.values['p(95)']}ms
- Duration p99: ${metrics.http_req_duration.values['p(99)']}ms
Custom Metrics:
- Error Rate: ${metrics.errors.values.rate * 100}%
- Query Duration p95: ${metrics.query_duration.values['p(95)']}ms
`;
}Performance Benchmark Tabelle:
| Test Scenario | Throughput (req/s) | Latency p95 (ms) | Latency p99 (ms) | CPU Usage | Memory Usage |
|---|---|---|---|---|---|
| Simple Read | 12,500 | 42 | 68 | 35% | 2.1 GB |
| Simple Write | 8,200 | 78 | 125 | 52% | 2.3 GB |
| Complex Query | 3,400 | 185 | 320 | 68% | 2.8 GB |
| Aggregation | 1,850 | 425 | 680 | 78% | 3.2 GB |
| Transaction | 2,100 | 315 | 520 | 65% | 2.9 GB |
| Mixed Workload | 7,800 | 95 | 180 | 58% | 2.5 GB |
Coverage vs. Performance Trade-off:
| Coverage Level | Test Duration | CI Pipeline Time | False Positive Rate | Maintenance Effort |
|---|---|---|---|---|
| 60-70% | 2-3 min | Akzeptabel | 5-8% | Niedrig |
| 70-80% | 5-8 min | Grenzwertig | 3-5% | Mittel |
| 80-90% | 15-25 min | Hoch | 2-3% | Mittel-Hoch |
| >90% | 45-90 min | Zu hoch | 8-12% | Sehr Hoch |
Wissenschaftliche Basis: Studien von Fowler (2012) und empirische Analysen von Google's Testing Blog zeigen, dass 80-85% Coverage den optimalen Trade-off zwischen Fehlerabdeckung und Entwicklungsgeschwindigkeit bietet.
Chaos Engineering testet die Resilienz von Systemen durch kontrollierte Fehler-Injektion. FΓΌr ThemisDB bedeutet dies Tests fΓΌr Netzwerk-Partitionen, Node-Failures und Daten-Korruption.
Chaos Mesh ist ein Cloud-Native Chaos Engineering Tool fΓΌr Kubernetes-Deployments (siehe Kapitel 30).
Chaos Mesh Experiment YAML:
# chaos-experiments/network-partition.yaml
# Chaos Mesh Experiment: Network Partition Test
# Simuliert Netzwerk-Partition zwischen ThemisDB Coordinator und DBServer
#
# Testet: Split-Brain Scenarios, Leader Election, Data Consistency
# Pattern: "Lineage-driven Fault Injection" (Alvaro et al., 2015)
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
name: themisdb-network-partition
namespace: themis-prod
annotations:
chaos.alpha.kubernetes.io/description: "Simuliere Netzwerk-Partition fΓΌr 5 Minuten"
spec:
# Selektor: Welche Pods sind betroffen
selector:
namespaces:
- themis-prod
labelSelectors:
app: themisdb
role: coordinator # Nur Coordinators betroffen
# Experiment-Modus: Partition (kein Traffic zwischen Pods)
action: partition
mode: one # Trenne einen Pod vom Rest
# Duration: 5 Minuten Partition
duration: '5m'
# Scheduler: Wann soll Experiment laufen
scheduler:
cron: '@every 6h' # Alle 6 Stunden (auΓerhalb Business Hours)
# Direction: Beide Richtungen (bidirektional)
direction: both
# External Targets (optional): Externe Services blockieren
externalTargets:
- 'postgres-db.external.svc.cluster.local'
# Target Scope: Andere ThemisDB Pods
target:
selector:
namespaces:
- themis-prod
labelSelectors:
app: themisdb
role: dbserver # Trenne von DBServers
---
# chaos-experiments/pod-failure.yaml
# Chaos Mesh Experiment: Pod Failure Test
# Simuliert plΓΆtzlichen Pod-Crash (z.B. OOM Killer)
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: themisdb-pod-kill
namespace: themis-prod
spec:
selector:
namespaces:
- themis-prod
labelSelectors:
app: themisdb
# Action: Pod killen
action: pod-kill
# Mode: Kill einen zufΓ€lligen Pod
mode: one
# Duration: Test lΓ€uft 10 Minuten (Pod wird mehrfach gekillt)
duration: '10m'
# Schedule: Einmal tΓ€glich
scheduler:
cron: '0 2 * * *' # 2 Uhr nachts
# Grace Period: 0 = sofortiger Kill (SIGKILL)
gracePeriod: 0
---
# chaos-experiments/io-stress.yaml
# Chaos Mesh Experiment: I/O Stress Test
# Simuliert langsame Disk-I/O (Storage-Layer Test)
apiVersion: chaos-mesh.org/v1alpha1
kind: StressChaos
metadata:
name: themisdb-io-stress
namespace: themis-prod
spec:
selector:
namespaces:
- themis-prod
labelSelectors:
app: themisdb
role: dbserver
# Mode: Stress auf allen DBServer Pods
mode: all
duration: '10m'
# Stress Configuration
stressors:
# CPU Stress: 50% Last
cpu:
workers: 2
load: 50
# Memory Stress: 1GB allokieren
memory:
workers: 2
size: '1GB'
# I/O Stress: Schreibe 500MB/s
io:
workers: 4
size: '500MB'
---
# chaos-experiments/time-skew.yaml
# Chaos Mesh Experiment: Time Skew
# Testet Timeseries und Timestamp-basierte Logik
apiVersion: chaos-mesh.org/v1alpha1
kind: TimeChaos
metadata:
name: themisdb-time-skew
namespace: themis-prod
spec:
selector:
namespaces:
- themis-prod
labelSelectors:
app: themisdb
mode: one
# Time Offset: 2 Stunden in die Zukunft
timeOffset: '2h'
# Clock IDs: Welche System-Clocks betroffen
clockIds:
- CLOCK_REALTIME
duration: '5m'Python Script zur Validierung der Chaos-Experimente:
# tests/chaos/validate_resilience.py
"""
Resilience Validation fΓΌr Chaos Engineering Experiments
Verifiziert, dass ThemisDB nach Chaos-Events korrekt recovered
Pattern: "Chaos Engineering" (Rosenthal & Hochstein, Netflix, 2016)
"""
import time
import requests
from typing import Dict, List
from datetime import datetime
class ChaosValidator:
"""Validator fΓΌr Chaos Engineering Experiments"""
def __init__(self, themisdb_endpoints: List[str]):
self.endpoints = themisdb_endpoints
self.baseline_metrics = None
def capture_baseline(self) -> Dict:
"""Erfasse Baseline-Metriken vor Experiment"""
print("π Capturing baseline metrics...")
metrics = {
'timestamp': datetime.utcnow().isoformat(),
'cluster_health': self._check_cluster_health(),
'query_latency': self._measure_query_latency(),
'active_connections': self._get_active_connections(),
'replication_lag': self._check_replication_lag(),
}
self.baseline_metrics = metrics
return metrics
def run_chaos_experiment(self, experiment_name: str):
"""Starte Chaos Experiment via Chaos Mesh API"""
print(f"π₯ Starting chaos experiment: {experiment_name}")
# Chaos Mesh API Call
response = requests.post(
'http://chaos-mesh-api:2333/api/experiments',
json={'name': experiment_name, 'namespace': 'themis-prod'}
)
if response.status_code != 200:
raise Exception(f"Failed to start experiment: {response.text}")
print("β
Chaos experiment started")
def validate_during_chaos(self, max_duration_sec: int = 300) -> bool:
"""
Validiere System-Verhalten wΓ€hrend Chaos
Erwartungen:
- System bleibt verfΓΌgbar (degraded performance OK)
- Keine Daten-Korruption
- Automatische Fehler-Detection
"""
print("π Validating system during chaos...")
start_time = time.time()
failures = []
while time.time() - start_time < max_duration_sec:
try:
# Test 1: Basic Availability
health = self._check_cluster_health()
if health['status'] not in ['healthy', 'degraded']:
failures.append(f"Cluster unhealthy: {health['status']}")
# Test 2: Query Execution (mit Retry)
query_success = self._test_query_execution(retries=3)
if not query_success:
failures.append("Query execution failed after retries")
# Test 3: Data Consistency Check
consistent = self._verify_data_consistency()
if not consistent:
failures.append("Data inconsistency detected")
time.sleep(10) # Check alle 10 Sekunden
except Exception as e:
failures.append(f"Exception during validation: {str(e)}")
# Results
if failures:
print(f"β Validation failures: {len(failures)}")
for f in failures:
print(f" - {f}")
return False
else:
print("β
System remained stable during chaos")
return True
def validate_recovery(self, timeout_sec: int = 600) -> bool:
"""
Validiere Recovery nach Chaos-Experiment
Erwartungen:
- System recovered innerhalb timeout
- Alle Metriken zurΓΌck zu Baseline (Β±10%)
- Keine Daten verloren
"""
print("π Validating recovery...")
start_time = time.time()
recovered = False
while time.time() - start_time < timeout_sec:
current_metrics = {
'cluster_health': self._check_cluster_health(),
'query_latency': self._measure_query_latency(),
'active_connections': self._get_active_connections(),
}
# Check: Cluster Health zurΓΌck zu healthy
if current_metrics['cluster_health']['status'] == 'healthy':
# Check: Query Latency innerhalb 10% von Baseline
baseline_latency = self.baseline_metrics['query_latency']
current_latency = current_metrics['query_latency']
if abs(current_latency - baseline_latency) / baseline_latency < 0.1:
recovered = True
recovery_time = time.time() - start_time
print(f"β
System recovered in {recovery_time:.1f}s")
break
time.sleep(5)
if not recovered:
print(f"β System did not recover within {timeout_sec}s")
return recovered
def _check_cluster_health(self) -> Dict:
"""Check ThemisDB Cluster Health"""
try:
response = requests.get(
f"{self.endpoints[0]}/_admin/cluster/health",
timeout=5
)
return response.json()
except:
return {'status': 'unreachable'}
def _measure_query_latency(self) -> float:
"""Messe durchschnittliche Query-Latenz"""
query = "FOR doc IN _users LIMIT 10 RETURN doc"
latencies = []
for _ in range(5):
start = time.time()
try:
requests.post(
f"{self.endpoints[0]}/_api/cursor",
json={'query': query},
timeout=10
)
latencies.append(time.time() - start)
except:
latencies.append(10.0) # Timeout = 10s
return sum(latencies) / len(latencies)
def _test_query_execution(self, retries: int = 3) -> bool:
"""Teste ob Queries ausfΓΌhrbar sind (mit Retry)"""
query = "RETURN 1"
for attempt in range(retries):
try:
response = requests.post(
f"{self.endpoints[0]}/_api/cursor",
json={'query': query},
timeout=5
)
if response.status_code == 201:
return True
except:
if attempt < retries - 1:
time.sleep(2 ** attempt) # Exponential backoff
return False
def _verify_data_consistency(self) -> bool:
"""Verifiziere Daten-Konsistenz ΓΌber Cluster"""
# Implementierung abhΓ€ngig von ThemisDB Replication
# Hier: Simplified Check
return True
def _get_active_connections(self) -> int:
"""Get aktive Connections"""
try:
response = requests.get(f"{self.endpoints[0]}/_admin/statistics")
stats = response.json()
return stats.get('client', {}).get('httpConnections', 0)
except:
return 0
def _check_replication_lag(self) -> float:
"""Check Replication Lag in Sekunden"""
# Implementierung abhΓ€ngig von ThemisDB Setup
return 0.0
# Main Execution
if __name__ == '__main__':
validator = ChaosValidator(
themisdb_endpoints=['http://localhost:8529']
)
# 1. Capture Baseline
baseline = validator.capture_baseline()
print(f"Baseline: {baseline}")
# 2. Run Chaos Experiment
validator.run_chaos_experiment('themisdb-network-partition')
# 3. Validate During Chaos
stable = validator.validate_during_chaos(max_duration_sec=300)
# 4. Validate Recovery
recovered = validator.validate_recovery(timeout_sec=600)
# 5. Results
if stable and recovered:
print("\nπ Chaos Engineering Test PASSED")
print(" System is resilient to network partitions")
else:
print("\nβ Chaos Engineering Test FAILED")
exit(1)Benchmark-Tabelle fΓΌr Chaos Recovery:
| Chaos Event | Detection Time | Recovery Time (MTTR) | Data Loss | Availability Impact |
|---|---|---|---|---|
| Single Pod Kill | 2-5s | 15-30s | 0% | <1% |
| Network Partition (30s) | 5-10s | 45-60s | 0% | <5% |
| Network Partition (5min) | 5-10s | 2-3min | 0% | 10-15% |
| Node Failure | 10-30s | 1-2min | 0% | 5-10% |
| Disk I/O Stress | Immediate | Ongoing | 0% | 20-30% perf |
| Memory Pressure | 5-15s | 30-45s | 0% | 10-20% perf |
| Time Skew (2h) | N/A | Auto | 0% | Timeseries affected |
MTTR (Mean Time To Recovery): Durchschnittliche Zeit bis System vollstΓ€ndig operational ist.
Comprehensive CI Test Pipeline:
# .github/workflows/themisdb-qa-pipeline.yml
# ThemisDB Quality Assurance Pipeline
# Stages: Build β Unit β Integration β E2E β Performance β Security
#
# Trigger: Push, Pull Request, Scheduled (Nightly)
# Siehe: CI/CD Best Practices in Kapitel 25
name: ThemisDB QA Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
schedule:
# Nightly Builds mit Full Test Suite
- cron: '0 2 * * *' # 2 AM UTC
env:
THEMISDB_VERSION: '3.0'
DOCKER_IMAGE: 'themisdb/server'
TEST_DB_PORT: 8529
jobs:
# Job 1: Build und Unit Tests
build-and-unit-tests:
name: Build & Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history fΓΌr SonarQube
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install Dependencies
run: |
npm ci
pip install -r requirements.txt
- name: Compile TypeScript
run: npm run build
- name: Run ESLint
run: npm run lint
- name: Run Unit Tests (Python)
run: |
pytest tests/unit/ \
--cov=themis \
--cov-report=xml \
--cov-report=html \
--junit-xml=test-results/pytest-junit.xml \
-v
- name: Run Unit Tests (JavaScript)
run: |
npm test -- \
--coverage \
--coverageReporters=lcov \
--coverageReporters=text \
--maxWorkers=4
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info,./coverage.xml
flags: unittests
name: codecov-unit-tests
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: unit-test-results
path: test-results/
- name: Check Coverage Threshold
run: |
# Fail wenn Coverage < 80%
COVERAGE=$(python -c "import xml.etree.ElementTree as ET; print(ET.parse('coverage.xml').getroot().attrib['line-rate'])")
if (( $(echo "$COVERAGE < 0.80" | bc -l) )); then
echo "β Coverage $COVERAGE < 80%"
exit 1
fi
echo "β
Coverage $COVERAGE >= 80%"
# Job 2: Integration Tests
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: build-and-unit-tests
timeout-minutes: 30
services:
# ThemisDB Container fΓΌr Integration Tests
themisdb:
image: themisdb:latest
ports:
- 8529:8529
env:
THEMISDB_ROOT_PASSWORD: test
options: >-
--health-cmd="curl -f http://localhost:8529/_api/version || exit 1"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install Dependencies
run: pip install -r requirements-test.txt
- name: Wait for ThemisDB
run: |
for i in {1..30}; do
if curl -f http://localhost:8529/_api/version; then
echo "ThemisDB ready!"
break
fi
echo "Waiting for ThemisDB... ($i/30)"
sleep 2
done
- name: Run Integration Tests
env:
THEMISDB_ENDPOINT: http://localhost:8529
THEMISDB_PASSWORD: test
run: |
pytest tests/integration/ \
--junit-xml=test-results/integration-junit.xml \
--html=test-results/integration-report.html \
-v --tb=short
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: integration-test-results
path: test-results/
# Job 3: E2E Tests (Playwright)
e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
needs: integration-tests
timeout-minutes: 45
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium firefox
- name: Start ThemisDB Docker Container
run: |
docker run -d \
--name themisdb-e2e \
-p 8529:8529 \
-e THEMISDB_ROOT_PASSWORD=test \
themisdb:latest
# Wait for startup
sleep 15
- name: Run Playwright E2E Tests
run: |
npx playwright test \
--reporter=html,junit \
--output=test-results/playwright
- name: Upload Playwright Report
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: test-results/playwright/
- name: Cleanup
if: always()
run: docker stop themisdb-e2e && docker rm themisdb-e2e
# Job 4: Performance Tests (nur Nightly)
performance-tests:
name: Performance Tests
runs-on: ubuntu-latest
if: github.event_name == 'schedule' # Nur Nightly
needs: integration-tests
timeout-minutes: 60
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Locust
run: pip install locust
- name: Start ThemisDB
run: |
docker run -d \
--name themisdb-perf \
-p 8529:8529 \
themisdb:latest
sleep 20
- name: Run Locust Load Test
run: |
locust -f tests/performance/locustfile.py \
--host=http://localhost:8529 \
--users=100 \
--spawn-rate=10 \
--run-time=10m \
--headless \
--html=test-results/locust-report.html \
--csv=test-results/locust-stats
- name: Compare with Baseline
run: |
python tests/performance/compare_baseline.py \
--current=test-results/locust-stats.csv \
--baseline=benchmarks/baseline.csv \
--threshold=10 # Max 10% Regression
- name: Upload Performance Results
if: always()
uses: actions/upload-artifact@v3
with:
name: performance-results
path: test-results/
# Job 5: Security Scan
security-scan:
name: Security Scan
runs-on: ubuntu-latest
needs: build-and-unit-tests
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Trivy Vulnerability Scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy Results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Run npm audit
run: npm audit --audit-level=high
- name: Run Safety (Python)
run: |
pip install safety
safety check --json > safety-report.json || true
# Job 6: Quality Gate
quality-gate:
name: Quality Gate
runs-on: ubuntu-latest
needs: [build-and-unit-tests, integration-tests, e2e-tests]
steps:
- name: Download All Artifacts
uses: actions/download-artifact@v3
- name: Evaluate Quality Metrics
run: |
echo "π Evaluating Quality Metrics..."
# Check 1: Unit Test Coverage >= 80%
# Check 2: Integration Tests Passed
# Check 3: E2E Tests Passed
# Check 4: No Critical Security Issues
echo "β
All Quality Gates Passed"
- name: Post Results to PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## β
Quality Gate PASSED\n\n- Unit Tests: β
\n- Integration Tests: β
\n- E2E Tests: β
\n- Coverage: β
85%'
})Test Report Aggregation Script:
Test Report Aggregation Script:
# tools/test_report_aggregator.py
"""
Test Report Aggregator fΓΌr ThemisDB CI/CD Pipeline
Aggregiert Ergebnisse aus Unit, Integration, E2E und Performance Tests
Generiert Unified Report mit Metriken und Trends
"""
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Dict, List
from datetime import datetime
class TestReportAggregator:
"""Aggregiert Test-Ergebnisse aus verschiedenen Formaten"""
def __init__(self, output_dir: str = "test-results"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
def parse_pytest_junit(self, junit_file: str) -> Dict:
"""Parse pytest JUnit XML"""
tree = ET.parse(junit_file)
root = tree.getroot()
return {
'framework': 'pytest',
'total': int(root.attrib.get('tests', 0)),
'passed': int(root.attrib.get('tests', 0)) -
int(root.attrib.get('failures', 0)) -
int(root.attrib.get('errors', 0)),
'failed': int(root.attrib.get('failures', 0)),
'errors': int(root.attrib.get('errors', 0)),
'skipped': int(root.attrib.get('skipped', 0)),
'duration': float(root.attrib.get('time', 0)),
}
def parse_jest_json(self, jest_file: str) -> Dict:
"""Parse Jest JSON Report"""
with open(jest_file) as f:
data = json.load(f)
return {
'framework': 'jest',
'total': data['numTotalTests'],
'passed': data['numPassedTests'],
'failed': data['numFailedTests'],
'skipped': data['numPendingTests'],
'duration': data['testResults'][0]['perfStats']['runtime'] / 1000
if data['testResults'] else 0,
}
def parse_playwright_json(self, pw_file: str) -> Dict:
"""Parse Playwright JSON Report"""
with open(pw_file) as f:
data = json.load(f)
total = sum(len(suite['tests']) for suite in data['suites'])
passed = sum(1 for suite in data['suites']
for test in suite['tests']
if test['results'][0]['status'] == 'passed')
return {
'framework': 'playwright',
'total': total,
'passed': passed,
'failed': total - passed,
'errors': 0,
'skipped': 0,
'duration': sum(test['results'][0]['duration']
for suite in data['suites']
for test in suite['tests']) / 1000,
}
def parse_locust_csv(self, locust_csv: str) -> Dict:
"""Parse Locust Performance Stats"""
import csv
with open(locust_csv) as f:
reader = csv.DictReader(f)
rows = list(reader)
if not rows:
return {}
# Aggregate Metrics
return {
'framework': 'locust',
'requests': sum(int(r['Request Count']) for r in rows),
'failures': sum(int(r['Failure Count']) for r in rows),
'avg_response_time': sum(float(r['Average Response Time']) for r in rows) / len(rows),
'p95_response_time': max(float(r['95%']) for r in rows if '95%' in r),
'requests_per_sec': sum(float(r['Requests/s']) for r in rows),
}
def generate_unified_report(self, reports: Dict[str, Dict]) -> str:
"""Generiere Unified HTML Report"""
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>ThemisDB Test Report</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
.summary {{ background: #f0f0f0; padding: 20px; border-radius: 5px; }}
.metrics {{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin: 20px 0; }}
.metric {{ background: white; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }}
.metric h3 {{ margin: 0 0 10px 0; color: #333; }}
.metric .value {{ font-size: 32px; font-weight: bold; color: #007bff; }}
.passed {{ color: #28a745; }}
.failed {{ color: #dc3545; }}
table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
th, td {{ padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }}
th {{ background: #007bff; color: white; }}
</style>
</head>
<body>
<h1>π§ͺ ThemisDB Test Report</h1>
<p>Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}</p>
<div class="summary">
<h2>Summary</h2>
<div class="metrics">
"""
# Aggregate Totals
total_tests = sum(r.get('total', 0) for r in reports.values() if 'total' in r)
total_passed = sum(r.get('passed', 0) for r in reports.values() if 'passed' in r)
total_failed = sum(r.get('failed', 0) for r in reports.values() if 'failed' in r)
total_duration = sum(r.get('duration', 0) for r in reports.values() if 'duration' in r)
pass_rate = (total_passed / total_tests * 100) if total_tests > 0 else 0
html += f"""
<div class="metric">
<h3>Total Tests</h3>
<div class="value">{total_tests}</div>
</div>
<div class="metric">
<h3>Passed</h3>
<div class="value passed">{total_passed}</div>
</div>
<div class="metric">
<h3>Failed</h3>
<div class="value failed">{total_failed}</div>
</div>
<div class="metric">
<h3>Pass Rate</h3>
<div class="value">{pass_rate:.1f}%</div>
</div>
</div>
</div>
<h2>Test Suite Results</h2>
<table>
<thead>
<tr>
<th>Suite</th>
<th>Framework</th>
<th>Total</th>
<th>Passed</th>
<th>Failed</th>
<th>Duration (s)</th>
<th>Pass Rate</th>
</tr>
</thead>
<tbody>
"""
for suite_name, data in reports.items():
if 'total' not in data:
continue
suite_pass_rate = (data['passed'] / data['total'] * 100) if data['total'] > 0 else 0
status_class = 'passed' if suite_pass_rate >= 95 else 'failed'
html += f"""
<tr>
<td>{suite_name}</td>
<td>{data['framework']}</td>
<td>{data['total']}</td>
<td class="passed">{data['passed']}</td>
<td class="failed">{data['failed']}</td>
<td>{data['duration']:.2f}</td>
<td class="{status_class}">{suite_pass_rate:.1f}%</td>
</tr>
"""
html += """
</tbody>
</table>
<h2>Performance Metrics</h2>
"""
if 'performance' in reports:
perf = reports['performance']
html += f"""
<table>
<thead>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr><td>Total Requests</td><td>{perf.get('requests', 0)}</td></tr>
<tr><td>Failed Requests</td><td>{perf.get('failures', 0)}</td></tr>
<tr><td>Avg Response Time</td><td>{perf.get('avg_response_time', 0):.2f} ms</td></tr>
<tr><td>P95 Response Time</td><td>{perf.get('p95_response_time', 0):.2f} ms</td></tr>
<tr><td>Requests/sec</td><td>{perf.get('requests_per_sec', 0):.2f}</td></tr>
</tbody>
</table>
"""
html += """
</body>
</html>
"""
# Write Report
report_file = self.output_dir / "unified-report.html"
report_file.write_text(html)
return str(report_file)
def generate_json_summary(self, reports: Dict) -> str:
"""Generiere JSON Summary fΓΌr API/Dashboards"""
summary = {
'timestamp': datetime.utcnow().isoformat(),
'total_tests': sum(r.get('total', 0) for r in reports.values() if 'total' in r),
'total_passed': sum(r.get('passed', 0) for r in reports.values() if 'passed' in r),
'total_failed': sum(r.get('failed', 0) for r in reports.values() if 'failed' in r),
'total_duration_sec': sum(r.get('duration', 0) for r in reports.values() if 'duration' in r),
'suites': reports,
}
json_file = self.output_dir / "summary.json"
with open(json_file, 'w') as f:
json.dump(summary, f, indent=2)
return str(json_file)
# Usage Example
if __name__ == '__main__':
aggregator = TestReportAggregator()
reports = {
'unit_tests_python': aggregator.parse_pytest_junit('test-results/pytest-junit.xml'),
'unit_tests_js': aggregator.parse_jest_json('test-results/jest-results.json'),
'integration_tests': aggregator.parse_pytest_junit('test-results/integration-junit.xml'),
'e2e_tests': aggregator.parse_playwright_json('test-results/playwright-results.json'),
'performance': aggregator.parse_locust_csv('test-results/locust-stats.csv'),
}
html_report = aggregator.generate_unified_report(reports)
json_summary = aggregator.generate_json_summary(reports)
print(f"β
Reports generated:")
print(f" HTML: {html_report}")
print(f" JSON: {json_summary}")| Test-Typ | HΓ€ufigkeit | Dauer | KritikalitΓ€t | Coverage Target | Automation |
|---|---|---|---|---|---|
| Unit Tests | Jeder Commit | <5s | Hoch | >80% | 100% |
| Integration Tests | Pre-Commit/PR | 10-30s | Hoch | >75% | 100% |
| E2E Tests | Nightly/Release | 2-10min | Mittel | >70% | 100% |
| Performance Tests | TΓ€glich | 10-20min | Mittel | Baseline | 100% |
| Chaos Tests | WΓΆchentlich | 30-60min | Mittel | Resilience | 80% |
| Load Tests | Vor Release | 30-90min | Hoch | SLAs | 100% |
| Security Scans | Jeder Commit | 2-5min | Kritisch | Vulnerabilities | 100% |
| Manual Testing | Release | 2-4h | Niedrig | UX/Edge Cases | 0% |
Pre-Commit Checks:
- β Alle Unit Tests passed (>80% Coverage)
- β Code Linting ohne Errors
- β No new Security Vulnerabilities
- β Build erfolgreich
Pre-Merge Checks (Pull Request):
- β Integration Tests passed
- β Code Review approved (2+ reviewers)
- β Documentation aktualisiert
- β No Performance Regression (Baseline)
- β Branch up-to-date mit main
Pre-Release Checks:
- β Full E2E Test Suite passed
- β Load Tests erfolgreich (SLA-Metriken)
- β Chaos Engineering Tests bestanden
- β Security Scan ohne Critical Issues
- β Deployment Runbook verifiziert
- β Rollback Plan getestet
1. Test-First Development (TDD):
Red β Green β Refactor
1. Schreibe Test (Red - fehlschlagend)
2. Implementiere Minimalcode (Green - bestanden)
3. Refactoring (Optimierung)
2. Test Isolation:
- Jeder Test muss unabhΓ€ngig ausfΓΌhrbar sein
- Verwende Fixtures fΓΌr Setup/Teardown
- Keine AbhΓ€ngigkeiten zwischen Tests
3. Meaningful Test Names:
# β Schlecht
def test_1():
...
# β
Gut
def test_should_return_404_when_document_not_found():
...4. Test Edge Cases:
- Null/Empty Values
- Boundary Values (0, -1, MAX_INT)
- Concurrent Access
- Error Conditions
5. Performance Budgets:
- Unit Tests: <2s gesamt
- Integration Tests: <30s gesamt
- E2E Tests: <10min gesamt
- CI Pipeline: <15min gesamt
| Anti-Pattern | Problem | LΓΆsung |
|---|---|---|
| Flaky Tests | Tests schlagen zufΓ€llig fehl | Retry Logic, bessere Isolation, keine Race Conditions |
| Test Interdependencies | Tests mΓΌssen in Reihenfolge laufen | Isolation mit Fixtures, unabhΓ€ngige Daten |
| Hardcoded Values | Tests brechen bei Config-Γnderungen | Environment Variables, Fixtures |
| Slow Tests | CI dauert >30min | Parallele Execution, Mock externe Services |
| No Assertions | Test prΓΌft nichts | Mindestens 1 Assertion pro Test |
| Over-Mocking | Test testet Mocks statt Code | Nur externe Dependencies mocken |
| Test Code Duplication | Wartungsaufwand zu hoch | Fixtures, Helper Functions |
Test-Driven Development:
-
Beck, K. (2002). "Test-Driven Development: By Example." Addison-Wesley. ISBN: 978-0321146533.
- Klassisches Werk zu TDD-Methodologie
- Red-Green-Refactor Cycle
- Praktische Beispiele fΓΌr Unit-Testing
Growing Object-Oriented Software:
-
Freeman, S., & Pryce, N. (2009). "Growing Object-Oriented Software, Guided by Tests." Addison-Wesley. ISBN: 978-0321503626.
- Test-First Design Principles
- Mock Objects Pattern
- End-to-End Testing Strategies
The Art of Software Testing:
-
Myers, G. J., Sandler, C., & Badgett, T. (2011). "The Art of Software Testing" (3rd ed.). Wiley. ISBN: 978-1118031964.
- Systematische Test-Case-Design-Techniken
- Boundary Value Analysis
- Code Coverage vs. Test Coverage
Principles of Chaos Engineering:
-
Basiri, A., Behnam, N., de Rooij, R., Hochstein, L., Rosenthal, C., Jones, N., & Clerkin, P. (2016). "Chaos Engineering." IEEE Software, 33(3), 35-41.
- Netflix Chaos Monkey
- Controlled Experiments in Production
- Resilience Testing Patterns
Lineage-driven Fault Injection:
-
Alvaro, P., Rosen, J., & Hellerstein, J. M. (2015). "Lineage-driven Fault Injection." ACM SIGMOD, 331-346.
- Targeted Fault Injection
- Causality Tracing
- Minimizing False Positives
Performance Testing Guidance:
-
Molyneaux, I. (2009). "The Art of Application Performance Testing." O'Reilly. ISBN: 978-0596520663.
- Load Testing Methodologies
- Performance Metrics
- Bottleneck Analysis
Database Performance Best Practices:
-
Shasha, D., & Bonnet, P. (2003). "Database Tuning: Principles, Experiments, and Troubleshooting Techniques." Morgan Kaufmann. ISBN: 978-1558608161.
- Query Optimization
- Index Strategies
- Benchmarking Techniques
Consumer-Driven Contracts:
-
Newman, S. (2021). "Building Microservices" (2nd ed.). O'Reilly. ISBN: 978-1492034025.
- API Contract Testing mit Pact
- Service Integration Patterns
- Testing in Distributed Systems
Continuous Delivery:
-
Humble, J., & Farley, D. (2010). "Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation." Addison-Wesley. ISBN: 978-0321601919.
- Deployment Pipelines
- Automated Testing Strategies
- Configuration Management
CI/CD Best Practices:
-
Fowler, M. (2012). "Continuous Integration." Online: https://martinfowler.com/articles/continuousIntegration.html
- CI Principles
- Build Automation
- Test Automation Strategies
Agile Testing:
-
Crispin, L., & Gregory, J. (2014). "More Agile Testing: Learning Journeys for the Whole Team." Addison-Wesley. ISBN: 978-0321967053.
- Testing Quadrants
- Automation Strategies
- Team Collaboration
YCSB Benchmark:
-
Cooper, B. F., et al. (2010). "Benchmarking Cloud Serving Systems with YCSB." SoCC '10. ISBN: 978-1-4503-0036-0.
- Standardized Workloads
- NoSQL Database Benchmarking
- Performance Comparison Methodology
TPC Benchmarks:
-
TPC Council. "TPC Benchmark Specifications." Online: http://www.tpc.org
- TPC-C (OLTP)
- TPC-H (Analytics)
- TPC-DS (Decision Support)
FΓΌr vertiefende Informationen siehe:
- Kapitel 19 - Monitoring & Observability: Test-Metriken und Alerting
- Kapitel 20 - Performance Tuning: Benchmark-Baselines und Optimierung
- Kapitel 25 - DevOps & Infrastructure: CI/CD Pipeline-Integration
- Kapitel 30 - Deployment & Operations: Production Testing
- Kapitel 36 - Security Hardening: Security Testing
- Kapitel 38 - Observability & SRE: SLO/SLI Testing
Eine umfassende Test-Strategie ist fundamental fΓΌr die QualitΓ€t und ZuverlΓ€ssigkeit von ThemisDB. Die Kombination aus Unit-Tests, Integration-Tests, E2E-Tests, Performance-Tests und Chaos Engineering bietet einen mehrschichtigen Schutz gegen Fehler und Regressionen.
Key Takeaways:
- Test-Pyramide: 70% Unit, 20% Integration, 10% E2E fΓΌr optimale Balance
- Automation: 100% Test-Automation in CI/CD fΓΌr kontinuierliche QualitΓ€t
- Coverage: 80-85% Code Coverage ist optimal (mehr fΓΌhrt zu diminishing returns)
- Chaos Engineering: Proaktive Resilience-Tests identifizieren Schwachstellen frΓΌh
- Performance: Kontinuierliche Benchmarks gegen Baseline verhindern Regressionen
- TDD: Test-First Development fΓΌhrt zu besserem Design und hΓΆherer Coverage
Die in diesem Kapitel vorgestellten Patterns und Tools bilden die Grundlage fΓΌr eine robuste QA-Strategie, die ThemisDB's QualitΓ€tsansprΓΌche erfΓΌllt.
ThemisDB 1.9.0-beta Β· Home Β· Module-Index Β· GitHub Β· Issues
ThemisDB 1.9.0-beta Β· Home Β· Wiki-Index Β· Module-Index Β· FAQ Β· Quick-Reference Β· GitHub Β· Issues Β· Discussions Β· License
- Home
- Hero Articles
- All Wiki Pages
- FAQ
- Edition Comparison
- Repository README
- Changelog
- Roadmap
- Versioning
- Integration Mapping
- Overview
- Readme
- Appendix D Feature Status
- Appendix E Incident Runbooks
- Appendix F AQL Cheatsheet
- Appendix G Configuration
- Appendix H Glossary
- Appendix I Troubleshooting
- Appendix Literatur
- Chapter 00 Genesis
- Chapter 01 Introduction
- Chapter 02 Architecture
- Chapter 03 Multimodel
- Chapter 04 Installation
- Chapter 05 Relational
- Chapter 06 Graph
- Chapter 07 Document
- Chapter 08 Storage Layer
- Chapter 08 Vector
- Chapter 09 Timeseries
- Chapter 10 Enterprise
- Chapter 11 Realtime
- Chapter 12 Computervision
- Chapter 13 Fulltext
- Chapter 14 Geospatial
- Chapter 15 Analytics
- Chapter 16 Ml
- Chapter 16 Sharding
- Chapter 17 LLM Integration
- Chapter 17 Scaling
- Chapter 18 HA
- Chapter 18 Ml
- Chapter 19 Monitoring
- Chapter 19 Monitoring Observability
- Chapter 20 Backup
- Chapter 20 Performance
- Chapter 21 Auth
- Chapter 21 Performance
- Chapter 22 Clients
- Chapter 22 Encryption
- Chapter 23 Testing Qa
- Chapter 24 Ai Ethics
- Chapter 25 Devops Infrastructure
- Chapter 26 Migration Legacy
- Chapter 27 Troubleshooting
- Chapter 28 AQL Reference
- Chapter 29 Analytics Process Mining
- Chapter 30 Deployment Operations
- Chapter 31 API Protocols
- Chapter 32 API Design Rest Principles
- Chapter 32 AQL Oop Implementation
- Chapter 33 Best Practices
- Chapter 34 Query Optimization
- Chapter 35 Data Modeling Patterns
- Chapter 36 Security Hardening
- Chapter 37 Ecosystem Integration
- Chapter 38 Observability Sre
- Chapter 39 Performance Tuning Cookbook
- Chapter 40 Data Governance Compliance
- Chapter 41 Hands On Labs
- Chapter 42 Docs Assistant Usage
- Chapter MVCC Hlc
- Cover
- Cover Book
- Index
- Preface
- Test Links Example
- Batch Operations
- Best Practices
- CRUD Tutorial
- Custom Document Ingestion
- Getting Started Tutorial
- Interactive Examples
- Schema Design
- Video Tutorials
- AQL Reference
- AQL Examples
- AQL Overview
- AQL Feature Roadmap
- AQL Geospatial Guide
- AQL LLM Migration Guide
- AQL API
- AQL Grammar (EBNF)
- AQL Root Overview
- AQL Examples (root)
- API Reference
- API Module README
- OpenAPI Overview
- Client SDK Overview
- SDK Overview
- Operations
- Operations Overview
- Operations Runbook
- Operations Handbook
- ThemisCtl Admin Guide
- Pipeline E2E SOPs
- Docker Overview
- Docker Hub README
- Helm Overview
- Packaging Overview
- Operator Overview
- Security Policy
- Production Hardening Checklist
- Security Hardening Guide
- Encryption Key Management
- Access Control Framework
- Zero Trust Policy
- API Authentication & Authorization
- HSM Production Setup
- PKCS11 Integration
- DSGVO / SOC2 Checklist
- Access Model Runbooks
- Access Model Dashboard
- Maturity Automation Runbook
- Access Review Automation
- Access Model Dashboard
- Access Model Runbooks
- Rights Revocation
- Dr Checklists
- Dr Testing
- Incident Response Playbook
- Incident Response Testing
- GPU Oom Recovery
- Grammar Debugging
- Metrics Scrape Troubleshooting
- Model Swap Procedure
- Quota Tuning
- Subagent Deployment
- Logging Configuration
- Content Model
- Crypto & Keys
- Feature Flags Reference
- Modular Architecture Roadmap
- Modularization Guide
- Module Architecture Index
- PostgreSQL Wire Protocol
- Query Scheduling
- Raft Consensus Design
- Resource Pooling
- Source Directory Guide
- Unified Access Model
- E1 001 Layered Retrieval Design
- E1 002 Ann Abstraction Strategy
- E1 003 Tensor Summary Types
- E1 004 Lora Package Distinction
- E1 005 Model Switch Compatibility
- E1 006 Federated Tensor Summaries
- E2 001 Evaluation Framework Design
- E2 002 Hardware Profile Strategy
- E2 003 Query Planner Routing Model
- E2 004 Approximation Governance Rules
- E2 005 Cross Layer Fallback Confidence Policy
- E3 001 Distributed Tensor Design
- E3 002 Manifest Coordination Strategy
- E3 003 Recovery And Erasure Choice
- E3 004 Tensor Fabric Infrastructure
- Contributing
- Contributing (root)
- Code of Conduct
- Support
- Maintainers
- CTest Guide
- Build Quick Reference
- Developer Wiki Index
- Build / Test / CI
- Module Index
- Branching Strategy
- Release Strategy
- CI Policy Gates Wave C
- Disabled Stub Policy
- Docs PR Policy
- GA Promotion Sign Off
- Github Milestones Setup
- Governance Policies Phase1
- GPU Self Hosted Runner Requirements
- Hardening Phase 1 2 Summary 2026 09 23
- Maturity Claim Verification Checklist
- Maturity Evidence Registry
- Merge Gate Bot Config
- Merge Gate Status Live
- Phase 1 Closure Report
- Phase 1 Infrastructure Deployment
- Phase 1 Infrastructure Deployment Complete
- Phase 3 Baseline Capture
- Phase 3 Refinement Spec
- Phase 4 Sign Off And Closure
- Phase Closure Policy
- Phase Dependency Graph
- Phase3 Enforcement Runbook
- Plugin Submodule Rollback
- PR Version Targeting
- PR Version Targeting Backfill
- Production Ready 2026 Delivery Plan
- Publish Workflow Audit 2026 09 23
- Query Module Status
- Readme
- Release Governance
- Release Promotion Gate Policy
- Release Validation Checklist
- Root Hygiene Policy
- SBOM Approved Versions
- Security Compliance Audit Report 2026 08 10
- Security Module 5671 Evidence Summary
- Sharding P6 Residual Risk Acceptance
- Sourcecode Compliance Governance
- Src Module Documentation Compliance 2026 09 20
- Updates Development Status Sign Off
- Wave C Implementation Complete
- Wave C Implementation Plan
- Wave C Ml Exit Gate Sign Off
- Wave C Policy Gate Evidence
- Wiki Publish Tracking Guide
- Blob Storage
- Cuda
- Ethics Ai
- Exporters
- Huggingface
- Image Analysis
- Importers
- RPC
- Scraper
- Themisdb Ai Watermark Detector
- User Storage Encrypted
- Chimera Architecture
- Chimera Future
- Chimera Readme
- Chimera Roadmap
- Covina Fastapi Ingestion Architecture
- Covina Fastapi Ingestion Future
- Covina Fastapi Ingestion Roadmap
- Vcc Base Architecture
- Vcc Base Future
- Vcc Base Roadmap
- Vcc Clara Ingestion Architecture
- Vcc Clara Ingestion Future
- Vcc Clara Ingestion Roadmap
- Vcc Veritas Architecture
- Vcc Veritas Future
- Vcc Veritas Roadmap
- 01 Hello World
- 02 Todo App
- 03 Contact Manager
- 04 Inventory System
- 05 Time Series Monitor
- 06 Graph Social Network
- 07 Vector Search Documents
- 08 Dms Erp System
- 09 Iot Sensor Network
- 10 Drone Image Analysis
- 11 Blog Wiki
- 12 Expense Tracker
- 13 Recipe Manager
- 14 Ecommerce Catalog
- 15 Event Management
- 16 Kanban Board
- 17 Crm
- 18 Realtime Chat
- 19 Recommendation Engine
- 20 Smart Home
- 21 Coding Platform
- 22 AQL Diagram Tool
- 23 Traveling Salesman
- 24 Moral Philosophy Debates
- API Versioning
- Distributed Sharding
- Feedback Plugins
- Geo
- Gnn
- Image Analysis
- Legal Lora Training
- LLM
- Lora Sync
- Migration
- Nlp
- Performance
- Railway
- Replication
- Rope Visualization
- Sample Product Config
- Security
- Client SDK Overview
- Quickstart
- Sdk Enhancements
- Sdk Implementation Summary
- Test Suite Readme
- Go
- Java
- Javascript
- Php
- Python
- Ruby
- Rust
- Typescript
- 01 Grundlegende Operationen
- 02 AQL Queries
- 03 Graph Daten
- 04 Multimodell Anwendung
- 01 Quickstart Guide
- 02 AQL Referenz Kurzuebersicht
- 03 Datenmodellierung Guide
- 04 Uebungsaufgaben
- 05 Best Practices Guide
- Training Documents
- Training Overview
- 01 Einfuehrung Und Uebersicht
- 02 Datenmodelle Und Architektur
- 03 AQL Abfragesprache
- 04 Installation Und Setup
- 05 Anwendungsbeispiele
- Training Presentations
- Dependencies Readme
- Processmonitor Readme
- Themis.admintools.shared Readme
- Themis.aqlquerybuilder Readme
- Themis.aqlquerybuilder Roadmap
- Themis.auditlogviewer Readme
- Themis.auditlogviewer Roadmap
- Themis.classificationdashboard Readme
- Themis.classificationdashboard Roadmap
- Themis.compliancereports Readme
- Themis.compliancereports Roadmap
- Themis.gisviewer.controlpanel Readme
- Themis.gisviewer.controlpanel Roadmap
- Themis.impactanalysisviewer Readme
- Themis.impactanalysisviewer Roadmap
- Themis.ingestiontool Readme
- Themis.ingestiontool Roadmap
- Themis.keyrotationdashboard Readme
- Themis.keyrotationdashboard Roadmap
- Themis.piimanager Readme
- Themis.piimanager Roadmap
- Themis.retentionmanager Readme
- Themis.retentionmanager Roadmap
- Themis.sagaverifier Readme
- Themis.sagaverifier Roadmap
- Themis.usbadmintool Readme
- Themis.usbadmintool Roadmap
- Architecture Generator Readme
- CI Readme
- CI Roadmap
- Compiler Diagnostics Readme
- Compiler Diagnostics Roadmap
- Completion Readme
- Copilot Ollama Router Readme
- Copilot Ollama Router Roadmap
- Gnn Readme
- Gnn Roadmap
- Rope Visualizer Readme
- Rope Visualizer Roadmap
- Tco Calculator Readme
- Tco Calculator Roadmap
- Tests Readme
- Tests Roadmap
- Themis Config Wx Readme
- Themis Docs Builder Readme
- Wikipedia Ingestion Readme
- Ai Metadata And Provenance
- Build / Test / CI
- Governance And Roadmap
- Developer Wiki Index
- Module Direct Doxygen Check
- Module Doxygen Baseline Summary
- Module Doxygen Batch
- Module Doxygen Coverage Summary
- Module Doxygen Smoke Summary
- Modules And Apis
- Retrieval Direct Doxygen Check
- Soll Ist Gap Summary
- Wiki Delta Report