07 Mehrsprachige Projekte: C++ Codequalität und Testing
January 2026 (8688 Words, 49 Minutes)
1. Einführung: Aufbauend auf C++-Grundlagen
In der vorherigen Vorlesung (Teil 1) haben Sie die Grundlagen der C++-Entwicklung kennengelernt:
- Wie C++ zu nativem Maschinencode kompiliert wird (unter Umgehung der Python Virtual Machine)
- Der Build-Prozess: Präprozessor, Kompilierung, Linking
- CMake als plattformübergreifendes Build-Konfigurationswerkzeug
- Abhängigkeitsverwaltung mit
find_package()undFetchContent - Header/Source-Datei-Organisation
Diese Vorlesung konzentriert sich auf Codequalität:
Code zu schreiben, der kompiliert, ist nur die halbe Miete—wir brauchen Code, der lesbar, wartbar und gut getestet ist.
┌─────────────────────────────────────────────────────────────────┐
│ C++ QUALITY PIPELINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Source Code → clang-format → clang-tidy → Google Test → gcov │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ Your .cpp Consistent Bug-free Verified Measured │
│ files style code behavior coverage │
│ │
│ Just like Python: Ruff → Pyright → pytest → pytest-cov │
│ │
└─────────────────────────────────────────────────────────────────┘
Diese Vorlesung behandelt die C++-Äquivalente zu Ihren Python-Qualitätswerkzeugen:
- Code-Formatierung — clang-format (wie Black/Ruff)
- Statische Analyse — clang-tidy + Compiler-Warnungen (wie Pyright/Ruff)
- Unit-Testing — Google Test (wie pytest)
- Coverage-Reports — gcov/llvm-cov (wie pytest-cov)
Am Ende werden Sie die gleiche Qualitätssicherung für C++ haben, die Sie bereits für Python haben. Teil 3 wird Ihnen dann zeigen, wie Sie beide Sprachen in einem einzigen Projekt integrieren.
2. Lernziele
Am Ende dieser Vorlesung werden Sie in der Lage sein:
Code-Formatierung:
- clang-format anzuwenden, um einen konsistenten C++-Code-Stil durchzusetzen
.clang-format-Dateien zu konfigurieren unter Verwendung des Google C++ Style Guide
Statische Analyse:
- Compiler-Warnungen zu konfigurieren für GCC, Clang und MSVC
- clang-tidy zu verwenden für erweiterte statische Analyse und Modernisierungsprüfungen
Unit-Testing:
- Google Test-Tests zu schreiben mit
TEST(),EXPECT_*undASSERT_*Makros - Test-Fixtures zu verwenden für gemeinsames Setup und Teardown
Coverage:
- C++ Coverage-Reports zu generieren mit gcov/llvm-cov
- Coverage-Metriken zu interpretieren und ungetestete Codepfade zu identifizieren
Vergleich:
- Python-Qualitätswerkzeuge auf C++-Äquivalente abzubilden (Ruff → clang-format/clang-tidy, pytest → Google Test, pytest-cov → gcov)
3. Codequalität für C++
In Teil 1 haben wir gelernt, wie man C++-Code kompiliert, CMake für die Build-Konfiguration verwendet und Abhängigkeiten verwaltet. Aber Code zu schreiben, der kompiliert, ist nur die halbe Miete—wir brauchen auch Code, der lesbar, wartbar und fehlerfrei ist.
Sie kennen diese Disziplin bereits aus Python. Erinnern Sie sich an die pyproject.toml des Road-Profile-Viewers?
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "B", "C4", "UP"]
[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.12"
Diese Konfiguration erzwingt:
- Konsistente Formatierung (Zeilenlänge, Einrückung)
- Linting-Regeln (Fehlercodes wie E, W, F)
- Typprüfung (Pyright im strikten Modus)
Jetzt brauchen wir die gleiche Disziplin für C++. Aber es gibt eine Komplikation…
3.1 Das Problem: C++ hat kein PEP 8
In Python ist Codequalität bemerkenswert standardisiert:
┌─────────────────────────────────────────────────────────────────────┐
│ PYTHON CODE QUALITY ECOSYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Style Guide: PEP 8 (the standard, everyone agrees) │
│ │ │
│ ▼ │
│ Tool: Ruff (or Black + Flake8 + isort) │
│ │ │
│ ▼ │
│ Configuration: pyproject.toml (one file for everything) │
│ │ │
│ ▼ │
│ Result: Consistent code across all Python projects │
│ │
└─────────────────────────────────────────────────────────────────────┘
C++ ist fragmentierter:
┌─────────────────────────────────────────────────────────────────────┐
│ C++ CODE QUALITY ECOSYSTEM │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Style Guides: Google Style LLVM Style Mozilla Style │
│ GNU Style Chromium Linux Kernel │
│ MISRA C++ (and dozens more...) │
│ │ │
│ ▼ │
│ Tools: clang-format (formatting) │
│ clang-tidy (linting) │
│ cppcheck, cpplint, PVS-Studio, ... │
│ │ │
│ ▼ │
│ Configuration: .clang-format + .clang-tidy (separate files) │
│ No single standard configuration │
│ │ │
│ ▼ │
│ Result: Every project looks different │
│ │
└─────────────────────────────────────────────────────────────────────┘
Warum das wichtig ist:
Wenn Sie ein neues Python-Projekt klonen, können Sie den Code sofort lesen—er sieht alles wie PEP 8 aus. Wenn Sie ein C++-Projekt klonen, finden Sie möglicherweise:
- 2-Leerzeichen-Einrückung (Google) oder 4-Leerzeichen (GNU) oder Tabs (Linux-Kernel)
- Geschweifte Klammern auf gleicher Zeile oder neuer Zeile
camelCaseodersnake_caseoderPascalCasefür Funktionen- Unterschiedliche Include-Sortierungskonventionen
Die gute Nachricht: Die C++-Community hat sich auf zwei ausgezeichnete Werkzeuge aus dem LLVM-Projekt geeinigt:
- clang-format — Automatische Code-Formatierung (wie
ruff format) - clang-tidy — Statische Analyse und Linting (wie
ruff check)
Mit der richtigen Konfiguration können Sie in C++ die gleiche Konsistenz erreichen, die Sie aus Python gewohnt sind.
3.2 Der Google C++ Style Guide
Für diesen Kurs verwenden wir den Google C++ Style Guide—einen der am weitesten verbreiteten Standards. Google hat seinen internen Style Guide als Open Source veröffentlicht, und er wird jetzt verwendet von:
- Google (offensichtlich)
- Vielen Chromium-basierten Browsern (Chrome, Edge, Brave)
- TensorFlow und anderen Google ML-Projekten
- Vielen Open-Source-Projekten, die einen gut dokumentierten Standard wollen
Warum Google Style für diesen Kurs?
| Grund | Erklärung |
|---|---|
| Gut dokumentiert | Der Style Guide erklärt warum jede Regel existiert, nicht nur was zu tun ist |
| Tool-Unterstützung | clang-format hat eingebautes BasedOnStyle: Google |
| Python-freundlich | Verwendet snake_case für Funktionen/Variablen (wie Python!) |
| Modernes C++ | Aktualisiert für C++11/14/17/20-Features |
| Industrie-Akzeptanz | Google Style zu kennen ist nützlich für Ihre Karriere |
3.3 Namenskonventionen: Python vs. Google C++
So vergleichen sich die Namenskonventionen:
| Element | Python (PEP 8) | Google C++ | Beispiel |
|---|---|---|---|
| Funktionen | snake_case |
snake_case |
calculate_distance() |
| Variablen | snake_case |
snake_case |
ray_angle |
| Klassen | PascalCase |
PascalCase |
RoadProfile |
| Konstanten | ALL_CAPS |
kPascalCase |
Python: MAX_SPEEDC++: kMaxSpeed |
| Private Member | _leading_underscore |
trailing_underscore_ |
Python: self._dataC++: data_ |
Beachte: Funktionen und Variablen verwenden die gleiche Konvention! Das ist beabsichtigt—Googles Style Guide wurde von Python beeinflusst.
// Google C++ Style - feels familiar to Python developers
class RoadProfile {
public:
double calculate_elevation(double x_position) const;
private:
std::vector<double> elevation_data_; // trailing underscore for private
};
// compare to Python:
// class RoadProfile:
// def calculate_elevation(self, x_position: float) -> float:
//
// _elevation_data: list[float] # leading underscore for private
3.4 Formatierungskonventionen
Einige wichtige Unterschiede zu Python:
// Google C++ Style
// 2-space indentation (not 4 like Python)
if (condition) {
do_something();
do_something_else();
}
// Opening brace on same line (like Python's ":" at end of line)
void calculate_ray_line(double angle) {
// implementation
}
// Line length: 80 characters (configurable to 100/120)
// This matches what you configured in pyproject.toml: line-length = 120
Die Philosophie ist die gleiche: Konsistente Formatierung macht Code leichter lesbar. Die spezifischen Regeln unterscheiden sich leicht, aber die Disziplin ist identisch.
4. clang-format: Der C++-Formatter
In Python führen Sie ruff format (oder black) aus, um Ihren Code automatisch zu formatieren. In C++ ist das Äquivalent clang-format.
4.1 Wie clang-format mit Ruff zusammenhängt
Denke an clang-format als “Ruff format für C++”:
| Aspekt | Python (Ruff format) | C++ (clang-format) |
|---|---|---|
| Zweck | Python-Code automatisch nach PEP 8 formatieren | C++-Code automatisch nach gewähltem Stil formatieren |
| Config-Datei | pyproject.toml oder ruff.toml |
.clang-format |
| In-Place formatieren | ruff format . |
clang-format -i file.cpp |
| Nur prüfen (CI) | ruff format --check . |
clang-format --dry-run --Werror file.cpp |
| Basis-Stil | PEP 8 (implizit) | BasedOnStyle: Google (explizit) |
4.2 Konfigurationsvergleich: pyproject.toml vs .clang-format
Vergleichen wir die Formatierungskonfiguration deines Road-Profile-Viewers mit einer äquivalenten C++-Konfiguration:
Python (pyproject.toml):
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.format]
quote-style = "double"
line-ending = "lf"
indent-style = "space"
docstring-code-format = true
C++ (.clang-format):
# .clang-format
---
Language: Cpp
BasedOnStyle: Google
# Line length (like line-length = 120)
ColumnLimit: 100
# Indentation (like indent-style = "space")
IndentWidth: 2
UseTab: Never
# Quote style doesn't apply (C++ uses "" for strings, '' for chars)
# But we can control include style:
IncludeBlocks: Regroup
# Brace style
BreakBeforeBraces: Attach
# Short forms
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
# Template formatting
AlwaysBreakTemplateDeclarations: Yes
# Include ordering (like isort in Python)
IncludeCategories:
- Regex: '^<.*\.h>' # C headers first
Priority: 1
- Regex: '^<.*>' # C++ standard library
Priority: 2
- Regex: '^".*"' # Project headers last
Priority: 3
...
Wichtige Erkenntnis: Beide Konfigurationen lösen die gleichen Probleme—sie verwenden nur unterschiedliche Syntax. Der Abschnitt IncludeCategories in clang-format entspricht direkt dem, was isort (jetzt Teil von Ruff) für Python-Imports tut.
4.3 clang-format installieren
# Ubuntu/Debian
sudo apt install clang-format
# macOS
brew install clang-format
# Windows (with LLVM - includes clang-format and clang-tidy)
choco install llvm
# Or with winget:
winget install LLVM.LLVM
Installation überprüfen:
clang-format --version
# Output: clang-format version 17.0.6 (or similar)
4.4 clang-format ausführen
Der Workflow spiegelt das wider, was Sie mit Ruff machen:
Eine einzelne Datei formatieren (wie ruff format file.py):
clang-format -i src/geometry.cpp
Das -i-Flag bedeutet “in-place” (Datei modifizieren). Ohne es gibt clang-format den formatierten Code auf stdout aus.
Prüfen ohne zu modifizieren (für CI, wie ruff format --check):
clang-format --dry-run --Werror src/geometry.cpp
--dry-run: Datei nicht modifizieren--Werror: Nicht-Null-Exit-Code zurückgeben, wenn Formatierungsänderungen nötig sind
Alle C++-Dateien formatieren (wie ruff format .):
# Linux/macOS
find . -name '*.cpp' -o -name '*.hpp' | xargs clang-format -i
# Or use a more portable approach with CMake (we'll see this in CI)
4.5 Vorher und Nachher: clang-format in Aktion
Vor der Formatierung:
#include "geometry.hpp"
#include <cmath>
#include <vector>
#include <iostream>
double calculate_ray_line(double angle,double camera_x,double camera_y,double x_max){
double angle_rad=-angle*3.14159265359/180.0;
double slope=std::tan(angle_rad);
if(std::abs(std::cos(angle_rad))<1e-10){return 0.0;}
double x_end=x_max;
return slope*(x_end-camera_x)+camera_y;
}
Nach clang-format -i mit Google-Stil:
#include <cmath>
#include <iostream>
#include <vector>
#include "geometry.hpp"
double calculate_ray_line(double angle, double camera_x, double camera_y,
double x_max) {
double angle_rad = -angle * 3.14159265359 / 180.0;
double slope = std::tan(angle_rad);
if (std::abs(std::cos(angle_rad)) < 1e-10) {
return 0.0;
}
double x_end = x_max;
return slope * (x_end - camera_x) + camera_y;
}
Was sich geändert hat:
- Includes neu sortiert (Standardbibliothek zuerst, Projekt-Header zuletzt)
- Konsistente Abstände um Operatoren
- Öffnende Klammer auf gleicher Zeile
- Korrekte Einrückung (2 Leerzeichen)
- Lange Parameterliste korrekt umgebrochen
5. clang-tidy: Der C++-Linter
Während clang-format behandelt, wie Ihr Code aussieht, analysiert clang-tidy, was Ihr Code tut. Es findet Bugs, schlägt Verbesserungen vor und erzwingt Best Practices—genau wie ruff check es für Python tut.
5.1 Wie clang-tidy mit Ruff Check zusammenhängt
Erinnern Sie sich an die Ruff-Konfiguration in Ihrem Road-Profile-Viewer?
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N", "B", "C4", "UP"]
Jeder Buchstabe ist eine Kategorie von Prüfungen:
- E: pycodestyle-Fehler
- W: pycodestyle-Warnungen
- F: Pyflakes (wahrscheinliche Bugs)
- I: isort (Import-Sortierung)
- N: pep8-naming
- B: flake8-bugbear (wahrscheinliche Bugs und Design-Probleme)
- C4: flake8-comprehensions (Comprehensions vereinfachen)
- UP: pyupgrade (moderne Python-Syntax)
clang-tidy funktioniert genauso—es hat Kategorien von Prüfungen, die Sie aktivieren oder deaktivieren können:
| Python (Ruff) | C++ (clang-tidy) | Was es findet |
|---|---|---|
F (Pyflakes) |
bugprone-* |
Wahrscheinliche Bugs, undefiniertes Verhalten |
B (Bugbear) |
bugprone-* |
Subtile Bugs, gefährliche Muster |
C4 (Comprehensions) |
performance-* |
Performance-Verbesserungen |
UP (pyupgrade) |
modernize-* |
Moderne Sprachfeatures verwenden |
N (pep8-naming) |
readability-identifier-naming |
Namenskonventionen |
E, W (pycodestyle) |
readability-* |
Stil und Klarheit |
| (Kein direktes Äquivalent) | cppcoreguidelines-* |
C++ Core Guidelines (Sicherheit) |
5.2 Konfigurationsvergleich: pyproject.toml vs .clang-tidy
Python (pyproject.toml):
[tool.ruff.lint]
# Select which rule categories to enable
select = ["E", "W", "F", "I", "N", "B", "C4", "UP"]
# Make all rules auto-fixable
fixable = ["ALL"]
# Ignore specific rules
ignore = ["E501"] # Ignore line length (handled by formatter)
C++ (.clang-tidy):
# .clang-tidy
---
# Enable check categories (like select = [...])
Checks: >
-*,
bugprone-*,
performance-*,
modernize-*,
readability-*,
-modernize-use-trailing-return-type,
-readability-magic-numbers
# Treat specific warnings as errors (like --error-on in Ruff)
WarningsAsErrors: ''
# Configure specific checks (like per-rule settings in Ruff)
CheckOptions:
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.VariableCase
value: lower_case
...
Das Muster ist identisch:
- Beginne damit, alle Prüfungen zu deaktivieren (
-*oder mit leerer Auswahl starten) - Aktiviere gewünschte Kategorien (
bugprone-*oder"F") - Deaktiviere spezifische nervige Regeln (
-readability-magic-numbersoderignore = ["E501"])
5.3 Warum clang-tidy compile_commands.json braucht
Hier ist ein wichtiger Unterschied: clang-tidy muss Ihre Projektstruktur verstehen.
Python (Ruff): Kann jede .py-Datei sofort analysieren—Pythons Import-System ist standardisiert.
C++ (clang-tidy): Muss wissen:
- Wo befinden sich Header-Dateien? (
-I./include) - Welche Compiler-Flags werden verwendet? (
-std=c++20) - Welche Makros sind definiert? (
-DDEBUG)
CMake generiert eine compile_commands.json-Datei, die all diese Informationen enthält:
# Generate compile_commands.json
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# This creates build/compile_commands.json:
# [
# {
# "directory": "/path/to/project/build",
# "command": "g++ -std=c++20 -I../include -c ../src/geometry.cpp",
# "file": "../src/geometry.cpp"
# },
# ...
# ]
Jetzt weiß clang-tidy genau, wie jede Datei kompiliert werden soll, und kann sie korrekt analysieren.
5.4 clang-tidy ausführen
Schritt 1: Kompilierungsdatenbank generieren:
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
Schritt 2: Auf einer einzelnen Datei ausführen (wie ruff check file.py):
clang-tidy -p build src/geometry.cpp
Das -p build-Flag sagt clang-tidy, wo es compile_commands.json findet.
Schritt 3: Auf allen Quelldateien ausführen (wie ruff check .):
# Linux/macOS
find src -name '*.cpp' | xargs clang-tidy -p build
# Or use run-clang-tidy (parallel, faster)
run-clang-tidy -p build src/
5.5 Beispiel: Was clang-tidy findet
Ihr Code:
void process_data(std::vector<double> data) { // Pass by value (expensive copy!)
for (int i = 0; i < data.size(); i++) { // Signed/unsigned comparison
if (data[i] = 0.0) { // Assignment instead of comparison!
continue;
}
}
}
clang-tidy-Ausgabe:
geometry.cpp:1:25: warning: parameter 'data' is passed by value; consider
passing by const reference [performance-unnecessary-value-param]
geometry.cpp:2:21: warning: comparison of integers of different signs
[bugprone-signed-unsigned-comparison]
geometry.cpp:3:21: warning: using the result of an assignment as a condition
without parentheses [bugprone-assignment-in-if-condition]
Vergleiche mit Ruff, das ähnliche Probleme in Python findet:
def process_data(data):
for i in range(len(data)): # Ruff: Use enumerate() instead [C416]
if data[i] = 0.0: # Python: SyntaxError (caught by interpreter)
continue
Die Philosophie ist die gleiche: Bugs finden, bevor sie in die Produktion gelangen.
5.6 VS Code-Integration: clangd-Erweiterung
In Python haben Sie die Ruff-Erweiterung für VS Code installiert, um Echtzeit-Formatierung und Linting zu erhalten. Für C++ ist das Äquivalent clangd—ein Language Server, der die Leistung von clang-format und clang-tidy direkt in Ihre IDE bringt.
clangd installieren:
- Öffne VS Code Extensions (Strg+Umschalt+X)
- Suche nach “clangd” (von LLVM)
- Klicke auf Installieren
Was clangd bietet:
| Feature | Python (Ruff) | C++ (clangd) |
|---|---|---|
| Formatierung beim Speichern | Automatisch |
Automatisch |
| Echtzeit-Linting | Wellenlinien |
Wellenlinien |
| Schnellkorrekturen | Code-Aktionen |
Code-Aktionen |
| Gehe zur Definition | Pylance/Pyright |
clangd |
| Hover-Dokumentation | Typ-Hinweise |
Typ-Info + Doku |
VS Code-Einstellungsvergleich:
Python (settings.json):
{
// Ruff as the formatter
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true
},
// Enable Ruff linting
"ruff.lint.enable": true
}
C++ (settings.json):
{
// clangd as the formatter (uses .clang-format)
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd",
"editor.formatOnSave": true
},
// clangd settings
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--header-insertion=never"
]
}
Wichtige clangd-Argumente:
| Argument | Zweck | Ruff-Äquivalent |
|---|---|---|
--clang-tidy |
Echtzeit-Linting aktivieren | ruff.lint.enable: true |
--background-index |
Projekt für schnelle Navigation indizieren | (Pylance macht das automatisch) |
--header-insertion=never |
Includes nicht automatisch hinzufügen | (N/A für Python) |
--compile-commands-dir=build |
Wo compile_commands.json zu finden ist | (Python braucht das nicht) |
Der Workflow ist identisch:
- Code schreiben → clangd zeigt Echtzeit-Warnungen (gelbe/rote Wellenlinien)
- Datei speichern → clang-format formatiert automatisch
- Über Warnung hovern → Erklärung und Lösungsvorschlag sehen
- Auf Glühbirne klicken → Automatische Korrektur anwenden
Empfohlene Erweiterungen für C++-Entwicklung:
| Erweiterung | Zweck | Python-Äquivalent |
|---|---|---|
| clangd (LLVM) | Formatierung, Linting, Navigation | Ruff + Pylance |
| CMake Tools | Build, Debug, CMake konfigurieren | (uv/pip sind einfacher) |
| C/C++ Extension Pack | Debugger, zusätzliche Werkzeuge | Python Debugger |
Wichtig: Wenn Sie die Microsoft C/C++-Erweiterung installiert haben, müssen Sie möglicherweise deren IntelliSense deaktivieren, um Konflikte mit clangd zu vermeiden. Fügen Sie zu settings.json hinzu:
{
"C_Cpp.intelliSenseEngine": "disabled"
}
Die wichtige Erkenntnis: So wie Sie Ruff in VS Code konfiguriert haben, um Python beim Speichern zu formatieren und zu linten, macht clangd das Gleiche für C++. Die Konfigurationsdateien sind unterschiedlich (.clang-format + .clang-tidy vs pyproject.toml), aber das Entwicklererlebnis ist identisch.
6. Statische Codeanalyse
Statische Analyse untersucht Code ohne ihn auszuführen. In Python war dies eingeschränkt, weil Python dynamisch typisiert ist. In C++ ist statische Analyse mächtiger, weil die Sprache statisch typisiert ist.
6.1 Warum statische Codeanalyse?
Bevor wir uns mit den Werkzeugen beschäftigen, lass uns verstehen, warum statische Analyse wichtig ist:
1. Fehler vor der Laufzeit finden
Statische Analyse findet Probleme, die sonst erst zur Laufzeit auftreten würden (oder schlimmer noch, in der Produktion):
| Problemtyp | Ohne statische Analyse | Mit statischer Analyse |
|---|---|---|
| Null-Pointer-Dereferenzierung | Absturz in Produktion | Zur Compile-Zeit gefunden |
| Buffer-Overflow | Sicherheitslücke | Warnung vor dem Merge |
| Ressourcen-Leak | Speicherverbrauch steigt über Zeit | Im PR-Review erkannt |
| Typverwechslung | Undefiniertes Verhalten | Compiler-Fehler |
2. Code-Review-Aufwand reduzieren
Wenn statische Analyse Stilprobleme und offensichtliche Fehler automatisch findet, können sich menschliche Reviewer auf folgendes konzentrieren:
- Architekturentscheidungen
- Algorithmus-Korrektheit
- Geschäftslogik
- Grenzfälle
3. Konsistenz im Team durchsetzen
Ohne automatisierte Prüfungen hängt der Code-Stil davon ab, wer den PR reviewed. Mit statischer Analyse:
- Jeder PR wird gleich geprüft
- Neue Teammitglieder bekommen sofortiges Feedback
- Stil-Debatten werden zu “lass das Werkzeug entscheiden”
4. Kosten für Fehlerbehebung steigen über die Zeit
Kosten, um einen Fehler zu beheben:
Während des Codens: 1x (Sie sind eh schon da)
Im Code-Review: 5x (Kontextwechsel für Reviewer)
Während des Testens: 10x (Test schreiben, debuggen, fixen)
In Produktion: 100x (Incident-Response, Kundenauswirkung)
Statische Analyse findet Fehler in der 1x-Kosten-Phase.
6.2 CI-Integration: Der Schlüssel zu effektiver Analyse
Statische Analyse-Werkzeuge lokal installiert zu haben ist nützlich, aber die wahre Stärke kommt von der Integration in Ihre CI-Pipeline. Das Schlüsselprinzip:
Jeder gemergte PR muss die statische Analyse bestehen. Keine Ausnahmen.
Warum CI-Integration essentiell ist:
- Entwickler können es nicht überspringen - Lokale Prüfungen können umgangen werden; CI nicht
- Konsistente Umgebung - Gleiche Compiler-Version, gleiche Flags, gleiche Werkzeuge
- Für alle sichtbar - PR-Status zeigt Erfolg/Fehlschlag für alle Reviewer
- Historische Nachverfolgung - Qualitätstrends über Zeit sehen
Python CI-Beispiel (aus Vorlesung 2):
# .github/workflows/ci.yml
name: Python Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Run Ruff linter
run: uv run ruff check src/
- name: Run Ruff formatter check
run: uv run ruff format --check src/
- name: Run Pyright type checker
run: uv run pyright src/
C++ CI-Beispiel:
# .github/workflows/cpp-ci.yml
name: C++ Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure CMake
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
- name: Build with warnings as errors
run: cmake --build build -- -j$(nproc)
# Build schlägt fehl, wenn eine Warnung auftritt (-Werror)
- name: Run clang-tidy
run: |
find src -name '*.cpp' | xargs clang-tidy \
-p build \
--warnings-as-errors='*'
- name: Check formatting
run: |
find src -name '*.cpp' -o -name '*.hpp' | xargs clang-format --dry-run --Werror
Das PR-Gate-Muster:
Entwickler pusht Code
↓
CI läuft automatisch
↓
┌───────────────────────────────────┐
│ ✅ Build erfolgreich │
│ ✅ clang-format-Prüfung bestanden│
│ ✅ clang-tidy-Prüfung bestanden │
│ ✅ Alle Tests bestanden │
└───────────────────────────────────┘
↓
PR kann gemergt werden (grüner Haken)
Wenn eine Prüfung fehlschlägt, ist der PR blockiert, bis der Fehler behoben ist.
6.3 Monitoring und Dashboards
Prüfungen in CI laufen zu lassen ist Schritt eins. Schritt zwei ist, Ergebnisse sichtbar und umsetzbar zu machen.
GitHub Actions: Inline-Annotationen
GitHub Actions kann Warnungen direkt im PR-Diff anzeigen:
- name: Run clang-tidy with annotations
run: |
clang-tidy src/*.cpp 2>&1 | \
sed -E 's/^(.+):([0-9]+):([0-9]+): (warning|error): (.+)/::error file=\1,line=\2,col=\3::\5/'
Das erzeugt Inline-Annotationen:
src/geometry.cpp
Line 42: ⚠️ warning: variable 'x' is not initialized [cppcoreguidelines-init-variables]
SonarQube/SonarCloud: Quality Gates
Für größere Projekte bieten dedizierte Plattformen Dashboards:
| Metrik | Beschreibung | Typischer Schwellwert |
|---|---|---|
| Code Coverage | Prozentsatz des durch Tests ausgeführten Codes | > 80% |
| Duplizierte Zeilen | Copy-Paste-Codeblöcke | < 3% |
| Code Smells | Wartbarkeitsprobleme | A-Bewertung (beste) |
| Bugs | Wahrscheinliche Laufzeitfehler | 0 neue Bugs |
| Schwachstellen | Sicherheitsprobleme | 0 neue Schwachstellen |
Codecov: Coverage-Tracking
- name: Generate coverage report
run: |
cmake --build build --target coverage
# Generiert lcov.info
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
files: build/coverage/lcov.info
fail_ci_if_error: true
Codecov zeigt:
- Coverage pro Datei
- Coverage-Trends über Zeit
- PR-Auswirkung (“dieser PR verringert Coverage um 2%”)
GitHub-Statusprüfungen: Das Endergebnis
All diese Werkzeuge können Status an GitHub melden:
Pull Request #42: Add intersection calculation
┌────────────────────────────────────────────┐
│ ✅ build (ubuntu-latest) 2m 34s │
│ ✅ clang-tidy 1m 12s │
│ ✅ clang-format 0m 08s │
│ ✅ tests (ubuntu-latest) 0m 45s │
│ ✅ codecov/patch 80.5% │
│ ✅ codecov/project 85.2% │
└────────────────────────────────────────────┘
All checks have passed
Vergleich: Python vs C++ Monitoring
| Aspekt | Python | C++ |
|---|---|---|
| Linting | Ruff (schnell, integriert) | clang-tidy (umfassend) |
| Formatierung | Ruff format | clang-format |
| Typprüfung | Pyright | Compiler (eingebaut) |
| Coverage | pytest-cov | gcov / llvm-cov |
| CI-Integration | GitHub Actions + Codecov | GitHub Actions + Codecov |
6.4 Compiler-Warnungen als Analyse
Der C++-Compiler selbst ist Ihr erster statischer Analysator. Verschiedene Compiler haben unterschiedliche Warnungsoptionen.
6.5 Compiler-Warnungsoptionen
GCC:
# Grundlegende Warnungen (empfohlenes Minimum)
g++ -Wall -Wextra -c main.cpp
# Pedantisch (strikte Standardkonformität)
g++ -Wall -Wextra -Wpedantic -c main.cpp
# Warnungen als Fehler (CI-Konfiguration)
g++ -Wall -Wextra -Wpedantic -Werror -c main.cpp
Clang:
# Empfohlene Clang-Warnungen
clang++ -Wall -Wextra -Wpedantic -c main.cpp
MSVC (Windows):
# Warnungsstufe 4 (höchste empfohlene)
cl /W4 /WX main.cpp
6.6 Zusammenfassung Compiler-Warnungen
| Zweck | GCC | Clang | MSVC |
|---|---|---|---|
| Standard-Warnungen | -Wall -Wextra |
-Wall -Wextra |
/W4 |
| Strikte Standards | -Wpedantic |
-Wpedantic |
/permissive- |
| Warnungen als Fehler | -Werror |
-Werror |
/WX |
6.7 CMake-Konfiguration für Compiler-Warnungen
# CMakeLists.txt
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()
7. Test-Frameworks für C++
In Python verwenden Sie pytest. In C++ ist das Äquivalent Google Test (gtest). So wie Ruff der De-facto-Python-Linter ist, ist Google Test das De-facto-C++-Test-Framework.
7.1 Wie Google Test sich zu pytest verhält
Betrachte Google Test als “pytest für C++”:
| Aspekt | Python (pytest) | C++ (Google Test) |
|---|---|---|
| Zweck | Tests ausführen, Fehler melden | Tests ausführen, Fehler melden |
| Installation | uv add pytest --dev |
CMake FetchContent |
| Konfigurationsdatei | pyproject.toml |
CMakeLists.txt |
| Tests ausführen | pytest |
ctest |
| Ausführliche Ausgabe | pytest -v |
ctest --output-on-failure |
| Spezifischen Test ausführen | pytest -k "test_name" |
ctest -R "test_name" |
| Maintainer | Community |
Google Test ist:
- Open Source, aktiv von Google gepflegt
- Plattformübergreifend (Linux, macOS, Windows)
- Mit CMake integriert (wie pytest mit uv integriert)
- Von großen Projekten verwendet (Chromium, LLVM, TensorFlow)
7.2 Installations-Vergleich
Python: Als Dev-Dependency in pyproject.toml hinzufügen:
uv add pytest --dev
C++: In CMakeLists.txt mit FetchContent hinzufügen:
# CMakeLists.txt
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
# Für Windows: Verhindert Überschreiben der Compiler/Linker-Einstellungen des Elternprojekts
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(tests
tests/test_geometry.cpp
)
target_link_libraries(tests PRIVATE
geometry_core
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(tests)
Wichtige Erkenntnis: Beide verwenden ein “Dependencies deklarieren, Werkzeug lädt sie herunter”-Muster. uv sync lädt pytest herunter; cmake lädt Google Test herunter.
7.3 Teststruktur-Vergleich
Side-by-Side-Vergleich äquivalenter Tests:
Python (tests/test_geometry.py):
import pytest
from road_profile_viewer.geometry import calculate_ray_line
def test_calculate_ray_line_basic():
"""Test basic ray calculation."""
result = calculate_ray_line(45.0, 0.0, 2.0, 80.0)
assert len(result["x"]) == 2
assert len(result["y"]) == 2
def test_calculate_ray_line_horizontal():
"""Test horizontal ray (0 degrees)."""
result = calculate_ray_line(0.0, 0.0, 2.0, 80.0)
# Horizontal ray: y stays constant
assert result["y"][0] == result["y"][1]
C++ (tests/test_geometry.cpp):
#include <gtest/gtest.h>
#include "geometry.hpp"
namespace rpv = road_profile_viewer;
// Test basic ray calculation
TEST(GeometryTest, CalculateRayLineBasic) {
auto result = rpv::calculate_ray_line(45.0, 0.0, 2.0, 80.0);
EXPECT_EQ(result.x.size(), 2);
EXPECT_EQ(result.y.size(), 2);
}
// Test horizontal ray (0 degrees)
TEST(GeometryTest, CalculateRayLineHorizontal) {
auto result = rpv::calculate_ray_line(0.0, 0.0, 2.0, 80.0);
// Horizontal ray: y stays constant
EXPECT_DOUBLE_EQ(result.y[0], result.y[1]);
}
Muster-Mapping:
def test_xxx():→TEST(SuiteName, TestName)assert condition→EXPECT_TRUE(condition)assert x == y→EXPECT_EQ(x, y)- Docstrings → Kommentare (oder
SCOPED_TRACE()verwenden)
7.4 Assertions und Expectations
| pytest | Google Test | Verhalten bei Fehlschlag |
|---|---|---|
assert x == y |
EXPECT_EQ(x, y) |
Test fortsetzen |
assert x == y (kritisch) |
ASSERT_EQ(x, y) |
Test sofort stoppen |
assert x != y |
EXPECT_NE(x, y) |
Test fortsetzen |
assert x < y |
EXPECT_LT(x, y) |
Test fortsetzen |
assert x > y |
EXPECT_GT(x, y) |
Test fortsetzen |
assert abs(x - y) < 0.001 |
EXPECT_NEAR(x, y, 0.001) |
Test fortsetzen |
assert x == pytest.approx(y) |
EXPECT_DOUBLE_EQ(x, y) |
Test fortsetzen |
assert x is None |
EXPECT_FALSE(x.has_value()) |
Test fortsetzen |
assert x is not None |
ASSERT_TRUE(x.has_value()) |
Stoppen wenn None (kann nicht fortsetzen) |
EXPECT vs ASSERT - Der zentrale Unterschied:
In pytest stoppt jedes assert den Test bei Fehlschlag. Google Test gibt Ihnen eine Wahl:
EXPECT_*: Fehlschlag protokollieren aber fortsetzen (alle Probleme melden)ASSERT_*: Fehlschlag protokollieren und stoppen (späterer Code würde abstürzen)
TEST(GeometryTest, FindIntersection) {
auto result = rpv::find_intersection(x_road, y_road, 10.0);
// ASSERT: Muss bestehen, bevor wir auf result->x zugreifen können
ASSERT_TRUE(result.has_value()); // Stoppen wenn None
// EXPECT: Kann mehrere Eigenschaften prüfen
EXPECT_GT(result->x, 0.0); // Fortsetzen auch bei Fehlschlag
EXPECT_GT(result->distance, 0.0); // Läuft trotzdem
}
7.5 Test-Fixtures-Vergleich
Sowohl pytest als auch Google Test unterstützen Fixtures für gemeinsames Setup.
Python (pytest Fixtures):
import pytest
from road_profile_viewer.geometry import find_intersection
@pytest.fixture
def road_profile():
"""Shared test data."""
return {
"x": [0, 10, 20, 30, 40],
"y": [0, 2, 4, 3, 5]
}
def test_find_intersection_exists(road_profile):
result = find_intersection(
road_profile["x"],
road_profile["y"],
angle=10.0
)
assert result is not None
assert result["distance"] > 0
def test_find_intersection_no_hit(road_profile):
# Ray pointing up, never hits road
result = find_intersection(
road_profile["x"],
road_profile["y"],
angle=-45.0 # Pointing upward
)
assert result is None
C++ (Google Test Fixtures):
class GeometryTest : public ::testing::Test {
protected:
void SetUp() override {
// Shared test data (like @pytest.fixture)
x_road = {0, 10, 20, 30, 40};
y_road = {0, 2, 4, 3, 5};
}
// Fixture data available to all tests
std::vector<double> x_road;
std::vector<double> y_road;
};
// Use TEST_F (F = Fixture) instead of TEST
TEST_F(GeometryTest, FindIntersectionExists) {
auto result = rpv::find_intersection(
x_road, y_road, 10.0, 0.0, 10.0
);
ASSERT_TRUE(result.has_value());
EXPECT_GT(result->distance, 0.0);
}
TEST_F(GeometryTest, FindIntersectionNoHit) {
// Ray pointing up, never hits road
auto result = rpv::find_intersection(
x_road, y_road, -45.0, 0.0, 10.0
);
EXPECT_FALSE(result.has_value());
}
Muster-Mapping:
@pytest.fixture→class TestName : public ::testing::Test- Fixture-Funktion →
SetUp()-Methode def test_xxx(fixture):→TEST_F(ClassName, TestName)
7.6 Parametrisierte Tests-Vergleich
Beide Frameworks unterstützen das Ausführen desselben Tests mit verschiedenen Eingaben.
Python (@pytest.mark.parametrize):
import pytest
@pytest.mark.parametrize("angle,expected_hits", [
(10.0, True), # Shallow angle, hits road
(45.0, True), # Steep angle, hits road
(-10.0, False), # Upward angle, misses
(90.0, False), # Vertical, edge case
])
def test_intersection_angles(road_profile, angle, expected_hits):
result = find_intersection(
road_profile["x"],
road_profile["y"],
angle=angle
)
assert (result is not None) == expected_hits
C++ (Google Test INSTANTIATE_TEST_SUITE_P):
struct AngleTestCase {
double angle;
bool expected_hits;
};
class AngleTest : public ::testing::TestWithParam<AngleTestCase> {
protected:
void SetUp() override {
x_road = {0, 10, 20, 30, 40};
y_road = {0, 2, 4, 3, 5};
}
std::vector<double> x_road;
std::vector<double> y_road;
};
TEST_P(AngleTest, IntersectionAngles) {
auto [angle, expected_hits] = GetParam();
auto result = rpv::find_intersection(
x_road, y_road, angle, 0.0, 10.0
);
EXPECT_EQ(result.has_value(), expected_hits);
}
INSTANTIATE_TEST_SUITE_P(
GeometryAngles,
AngleTest,
::testing::Values(
AngleTestCase{10.0, true}, // Shallow angle
AngleTestCase{45.0, true}, // Steep angle
AngleTestCase{-10.0, false}, // Upward angle
AngleTestCase{90.0, false} // Vertical
)
);
Wichtige Erkenntnis: Das Konzept ist identisch—dieselbe Testlogik mit verschiedenen Daten ausführen. pytest ist kompakter; Google Test ist expliziter.
7.7 Tests ausführen - Vergleich
| Aktion | Python (pytest) | C++ (ctest/gtest) |
|---|---|---|
| Alle Tests ausführen | pytest |
ctest --test-dir build |
| Ausführliche Ausgabe | pytest -v |
ctest --output-on-failure |
| Nach Namensmuster ausführen | pytest -k "ray" |
ctest -R "ray" |
| Spezifische Datei ausführen | pytest tests/test_geometry.py |
./build/tests --gtest_filter="Geometry*" |
| Bei erstem Fehlschlag stoppen | pytest -x |
ctest --stop-on-failure |
| Parallele Ausführung | pytest -n auto |
ctest -j$(nproc) |
Build- und Ausführungs-Workflow:
# Python
uv run pytest -v
# C++
cmake --build build
ctest --test-dir build --output-on-failure
# Oder das Test-Executable direkt ausführen für mehr Details
./build/tests
7.8 Test-Ausgabe-Vergleich
pytest-Ausgabe:
========================= test session starts ==========================
collected 4 items
tests/test_geometry.py::test_calculate_ray_line_basic PASSED [ 25%]
tests/test_geometry.py::test_calculate_ray_line_horizontal PASSED [ 50%]
tests/test_geometry.py::test_find_intersection_exists PASSED [ 75%]
tests/test_geometry.py::test_find_intersection_no_hit PASSED [100%]
========================== 4 passed in 0.12s ===========================
Google Test-Ausgabe:
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from GeometryTest
[ RUN ] GeometryTest.CalculateRayLineBasic
[ OK ] GeometryTest.CalculateRayLineBasic (0 ms)
[ RUN ] GeometryTest.CalculateRayLineHorizontal
[ OK ] GeometryTest.CalculateRayLineHorizontal (0 ms)
[ RUN ] GeometryTest.FindIntersectionExists
[ OK ] GeometryTest.FindIntersectionExists (0 ms)
[ RUN ] GeometryTest.FindIntersectionNoHit
[ OK ] GeometryTest.FindIntersectionNoHit (0 ms)
[----------] 4 tests from GeometryTest (0 ms total)
[==========] 4 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 4 tests.
Das Ausgabeformat unterscheidet sich, aber die Information ist dieselbe: Welche Tests liefen, welche bestanden, welche fehlschlugen, und wie lange es dauerte.
8. Coverage-Reports für C++
In Python verwenden Sie pytest-cov, um die Testabdeckung zu messen. In C++ sind die entsprechenden Werkzeuge gcov (GCC) und llvm-cov (Clang). Der Arbeitsablauf ist derselbe: Code instrumentieren, Tests ausführen, Report generieren.
8.1 Wie C++-Coverage mit pytest-cov zusammenhängt
| Aspekt | Python (pytest-cov) | C++ (gcov/llvm-cov) |
|---|---|---|
| Installation | uv add pytest-cov --dev |
In GCC/Clang eingebaut |
| Coverage aktivieren | pytest --cov=src |
CMake: -DENABLE_COVERAGE=ON |
| Mit Coverage ausführen | pytest --cov=src |
ctest (nach Coverage-Build) |
| Terminal-Report | --cov-report=term |
gcov oder llvm-cov report |
| HTML-Report | --cov-report=html |
genhtml (lcov) oder llvm-cov show |
| CI-Upload | Codecov Action | Codecov Action (dieselbe!) |
8.2 Coverage-Workflow-Vergleich
Python-Workflow:
# Install
uv add pytest-cov --dev
# Run tests with coverage
uv run pytest --cov=src --cov-report=term --cov-report=html
# View HTML report
open htmlcov/index.html
C++-Workflow:
# Configure with coverage enabled
cmake -B build -DENABLE_COVERAGE=ON
# Build
cmake --build build
# Run tests (this generates coverage data)
ctest --test-dir build
# Generate HTML report
lcov --capture --directory build --output-file coverage.info
genhtml coverage.info --output-directory htmlcov
# View HTML report
open htmlcov/index.html
Wichtige Erkenntnis: Die Schritte sind konzeptionell identisch:
- Coverage-Instrumentierung aktivieren
- Tests ausführen
- Menschenlesbaren Report generieren
8.3 CMake-Coverage-Konfiguration
Fügen Sie dies zu Ihrer CMakeLists.txt hinzu:
# CMakeLists.txt
option(ENABLE_COVERAGE "Enable code coverage" OFF)
if(ENABLE_COVERAGE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
# Add coverage flags (like pytest --cov)
add_compile_options(--coverage -O0 -g)
add_link_options(--coverage)
endif()
endif()
Vergleich mit pyproject.toml:
# pyproject.toml (Python)
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=term"
Beide konfigurieren Coverage in der Build-Konfigurationsdatei des Projekts.
8.4 gcov (GCC) vs llvm-cov (Clang)
| Aspekt | gcov (GCC) | llvm-cov (Clang) |
|---|---|---|
| Compiler | GCC (g++) | Clang (clang++) |
| Coverage-Flags | --coverage |
-fprofile-instr-generate -fcoverage-mapping |
| Rohdaten-Format | .gcda, .gcno |
.profraw |
| HTML-Generator | lcov + genhtml | llvm-cov show --format=html |
| CI-Kompatibilität | Codecov, Coveralls | Codecov, Coveralls |
gcov-Workflow (häufiger unter Linux):
# Build with GCC coverage
cmake -B build -DCMAKE_CXX_COMPILER=g++ -DENABLE_COVERAGE=ON
cmake --build build
# Run tests
ctest --test-dir build
# Generate lcov report
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage.info
# Generate HTML
genhtml coverage.info --output-directory htmlcov
llvm-cov-Workflow (bessere Source-Annotation):
# Build with Clang coverage
cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DENABLE_COVERAGE=ON
cmake --build build
# Run tests (generates .profraw)
ctest --test-dir build
# Merge profile data
llvm-profdata merge -sparse default.profraw -o coverage.profdata
# Generate report
llvm-cov show ./build/tests -instr-profile=coverage.profdata --format=html > htmlcov/index.html
8.5 Coverage-Report-Vergleich
pytest-cov Terminal-Ausgabe:
---------- coverage: platform linux, python 3.12.0 ----------
Name Stmts Miss Cover
-----------------------------------------------------
src/road_profile_viewer/__init__.py 2 0 100%
src/road_profile_viewer/geometry.py 45 3 93%
src/road_profile_viewer/visualization.py 78 12 85%
-----------------------------------------------------
TOTAL 125 15 88%
gcov/lcov Terminal-Ausgabe:
Reading tracefile coverage.info
Summary coverage rate:
lines......: 88.0% (110 of 125 lines)
functions..: 100.0% (12 of 12 functions)
branches...: 75.0% (30 of 40 branches)
Beide zeigen dieselbe Schlüsselmetrik: Welcher Prozentsatz deines Codes wurde von Tests ausgeführt.
8.6 CI-Integration-Vergleich
Python CI (.github/workflows/python-ci.yml):
- name: Run tests with coverage
run: uv run pytest --cov=src --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.xml
fail_ci_if_error: true
C++ CI (.github/workflows/cpp-ci.yml):
- name: Configure with coverage
run: cmake -B build -DENABLE_COVERAGE=ON
- name: Build
run: cmake --build build
- name: Run tests
run: ctest --test-dir build
- name: Generate coverage report
run: |
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' -o coverage.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: coverage.info
fail_ci_if_error: true
Wichtige Erkenntnis: Beide laden zum selben Codecov-Service hoch. Ihre Python- und C++-Coverage-Reports erscheinen auf demselben Dashboard, was es einfach macht, die gesamte Projektgesundheit zu verfolgen.
8.7 Coverage-Schwellenwerte
Python (in pyproject.toml oder pytest.ini):
[tool.coverage.report]
fail_under = 80
C++ (im CI-Workflow):
- name: Check coverage threshold
run: |
COVERAGE=$(lcov --summary coverage.info | grep "lines" | grep -oP '\d+\.\d+')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
Oder verwende den eingebauten Schwellenwert von Codecov:
# codecov.yml (same for Python and C++)
coverage:
status:
project:
default:
target: 80%
threshold: 1%
8.8 Zusammenfassung des Coverage-Workflows
uv add pytest-covpytest --covcoverage.xmlcmake -DCOVERAGE=ONctestlcov→ coverage.info9. Vergleich: Python vs. C++ Quality-Tools
Nachdem wir nun Code-Qualität, Testing und Coverage für beide Ökosysteme behandelt haben, fassen wir alles zusammen. Dieser umfassende Vergleich zeigt, wie sich die Disziplin, die Sie in Python gelernt haben, direkt auf C++ übertragen lässt.
9.1 Werkzeug-Vergleich
| Funktion | Python | C++ |
|---|---|---|
| Style Guide | PEP 8 | Google C++ Style Guide |
| Formatter | Ruff format, Black | clang-format |
| Linter | Ruff check, Pylint | clang-tidy |
| Type Checker | Pyright, mypy | Compiler (eingebaut) |
| Config-Datei | pyproject.toml |
.clang-format + .clang-tidy |
| Import-Sortierung | isort (Teil von Ruff) | clang-format IncludeCategories |
| Pre-Commit-Hook | ruff check --fix && ruff format |
clang-format -i && clang-tidy |
9.2 Workflow-Vergleich
| Workflow-Schritt | Python | C++ |
|---|---|---|
| Alle Dateien formatieren | ruff format . |
find . -name '*.cpp' | xargs clang-format -i |
| Formatierung prüfen (CI) | ruff format --check . |
clang-format --dry-run --Werror |
| Linter ausführen | ruff check . |
clang-tidy -p build src/*.cpp |
| Linter-Probleme beheben | ruff check --fix . |
clang-tidy -p build --fix src/*.cpp |
| Type-Check | pyright . |
(Der Compiler macht das automatisch) |
| IDE-Integration | VS Code Python Extension | VS Code C/C++ Extension, clangd |
9.3 Konfigurations-Philosophie
| Aspekt | Python | C++ |
|---|---|---|
| Konfigurations-Ort | Eine Datei: pyproject.toml |
Separate Dateien pro Werkzeug |
| Konfigurations-Format | TOML | YAML (clang-Tools) |
| Basis-Style | PEP 8 (impliziter Standard) | BasedOnStyle: Google (explizit) |
| Regel-Auswahl | select = ["E", "W", "F"] |
Checks: bugprone-*, performance-* |
| Regeln deaktivieren | ignore = ["E501"] |
-readability-magic-numbers |
9.4 CI/CD-Integration
| CI-Schritt | Python (GitHub Actions) | C++ (GitHub Actions) |
|---|---|---|
| Setup | uses: astral-sh/setup-uv@v4 |
sudo apt install clang-format clang-tidy |
| Format-Check | uv run ruff format --check . |
clang-format --dry-run --Werror src/*.cpp |
| Lint-Check | uv run ruff check . |
clang-tidy -p build src/*.cpp |
| Type-Check | uv run pyright |
(Teil von cmake --build) |
| Bei Fehler beenden | Automatisch (non-zero exit) | --warnings-as-errors='*' |
9.5 Testing-Vergleich
| Aspekt | Python (pytest) | C++ (Google Test) |
|---|---|---|
| Installieren | uv add pytest --dev |
CMake FetchContent |
| Tests ausführen | pytest |
ctest --test-dir build |
| Verbose | pytest -v |
ctest --output-on-failure |
| Tests filtern | pytest -k "ray" |
ctest -R "ray" |
| Assertions | assert x == y |
EXPECT_EQ(x, y) |
| Fixtures | @pytest.fixture |
class : public ::testing::Test |
| Parametrisieren | @pytest.mark.parametrize |
INSTANTIATE_TEST_SUITE_P |
9.6 Coverage-Vergleich
| Aspekt | Python (pytest-cov) | C++ (gcov/llvm-cov) |
|---|---|---|
| Installieren | uv add pytest-cov --dev |
In GCC/Clang eingebaut |
| Aktivieren | pytest --cov=src |
cmake -DENABLE_COVERAGE=ON |
| HTML-Report | --cov-report=html |
genhtml coverage.info |
| CI-Upload | codecov/codecov-action@v4 |
codecov/codecov-action@v4 |
| Schwellenwert | fail_under = 80 |
codecov.yml: target: 80% |
9.7 Die wichtigste Erkenntnis
┌─────────────────────────────────────────────────────────────────────┐
│ DIESELBE DISZIPLIN, ANDERE WERKZEUGE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CODE-QUALITÄT: │
│ Python: pyproject.toml → Ruff format → Ruff check → CI │
│ C++: .clang-format → clang-format → clang-tidy → CI │
│ │
│ TESTING: │
│ Python: pytest → assert x == y → @pytest.fixture │
│ C++: ctest → EXPECT_EQ(x,y) → ::testing::Test │
│ │
│ COVERAGE: │
│ Python: pytest --cov → htmlcov/ → Codecov │
│ C++: gcov/llvm-cov → genhtml → Codecov │
│ │
│ Die Werkzeuge sind unterschiedlich, aber der Workflow ist │
│ identisch: │
│ 1. Einmal im Projekt-Root konfigurieren │
│ 2. Automatisch ausführen (pre-commit, CI) │
│ 3. Konsistenter, hochwertiger, gut getesteter Code │
│ │
└─────────────────────────────────────────────────────────────────────┘
Was Sie in Python gelernt haben, überträgt sich direkt auf C++. Dasselbe mentale Modell — Werkzeuge konfigurieren, automatisch durchsetzen, gründlich testen, Coverage messen — gilt in beiden Ökosystemen. Sie lernen nicht etwas Neues; Sie wenden vertraute Konzepte mit anderer Syntax an.
10. Zusammenfassung
In dieser Vorlesung haben Sie gelernt, wie Sie dieselbe Qualitätsdisziplin auf C++ anwenden, die Sie bereits für Python verwenden:
10.1 Was Sie gelernt haben
Code-Formatierung:
- clang-format erzwingt automatisch einen konsistenten Style (wie Black/Ruff für Python)
- Der Google C++ Style Guide bietet einen gut dokumentierten Standard
- Die Konfiguration lebt in
.clang-formatim Projekt-Root
Statische Analyse:
- Compiler-Warnungen (
-Wall -Wextra -Wpedantic) fangen viele Fehler zur Compile-Zeit ab - clang-tidy bietet tiefere Analyse und Modernisierungs-Checks
- Die Konfiguration lebt in
.clang-tidyim Projekt-Root
Unit-Testing:
- Google Test bietet eine pytest-ähnliche Test-Erfahrung für C++
EXPECT_*läuft bei Fehlern weiter,ASSERT_*stoppt sofort- Test-Fixtures teilen Setup/Teardown über verwandte Tests
Coverage:
- gcov/llvm-cov messen, welchen Code Ihre Tests ausführen
- Derselbe Workflow wie in Python: Tests mit Coverage-Flags ausführen, HTML-Report generieren
- Codecov funktioniert identisch für Python- und C++-Coverage
Wichtige Erkenntnis:
Die Werkzeuge sind unterschiedlich, aber die Disziplin ist identisch:
Konfigurieren → Durchsetzen → Testen → Messen → Wiederholen
11. Reflexionsfragen
Bevor Sie zu Teil 3 übergehen, überlegen Sie sich diese Fragen:
-
Konfiguration: Wie würden Sie clang-format einrichten, um den bestehenden Code-Style Ihres Teams zu entsprechen?
-
Warnungen als Fehler: Was sind die Vor- und Nachteile der Verwendung von
-Werrorin Produktions- vs. Entwicklungs-Builds? -
Test-Design: Wann würden Sie
EXPECT_*vs.ASSERT_*in Google Test verwenden? Geben Sie ein Beispiel für jeden. -
Coverage-Lücken: Wenn eine Funktion 100% Zeilenabdeckung hat, aber immer noch einen Bug enthält, was könnte fehlen? Wie hilft Branch-Coverage?
-
Werkzeug-Vergleich: Sie treten einem neuen Team bei, das C++ verwendet. Wie würden Sie den Zweck von clang-tidy einem Python-Entwickler erklären, der Ruff kennt?
12. Was kommt als Nächstes
Teil 3: Praktische Implementierung wird Ihnen zeigen, wie Sie Python und C++ in einem einzigen Projekt kombinieren:
- Mono-Repo-Architektur — Python und C++ in einem Repository verwalten
- pybind11 — Python-Bindings für C++-Funktionen erstellen
- Praktische Implementierung — Geometrie-Funktionen vom Road Profile Viewer nach C++ portieren
- CI/CD — GitHub Actions für Multi-Language-Projekte
Am Ende von Teil 3 wird Ihr Road Profile Viewer C++-Implementierungen haben, die:
- 10-100x schneller als reines Python laufen
- Auf Embedded-Systeme deployed werden können
- Sich nahtlos mit Ihrem bestehenden Python-Code integrieren
┌─────────────────────────────────────────────────────────────────────┐
│ WAS IN TEIL 3 KOMMT │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Python Code C++ Code │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ calculate_ray_line │ │ calculate_ray_line │ │
│ │ find_intersection │ ── pybind11 ──> │ find_intersection │ │
│ │ (easy to read) │ │ (fast to run) │ │
│ └────────────────────┘ └────────────────────┘ │
│ │
│ Ihre Python-App ruft C++-Funktionen auf, als wären sie natives │
│ Python │
│ │
└─────────────────────────────────────────────────────────────────────┘