Test-Driven Development (TDD) verlagert das Schreiben von Tests zeitlich vor die eigentliche Implementierung. In diesem Notebook betrachten wir verschiedene Werkzeuge im Python-Ökosystem, um die Korrektheit und Robustheit unseres Codes von einfachen interaktiven Beispielen bis hin zu massenhaft generierten Randfällen sicherzustellen.
Dies ist eine logische Fortführung unserer Beschäftigung mit Lintern und Typisierung. Wenn Typ-Checks die strukturelle Korrektheit gewährleisten, sichern Tests die semantische Korrektheit.
# Vorbereitung: Virtuelle Umgebung und Tools installieren
!uv venv --quiet
!uv sync --quiet
!uv pip --quiet install faker hydra-core hypothesis nox pytest pytest-cov pytest-gremlinsDas durchgehende Beispiel: Berechnung der Shannon-Entropie¶
Wir nutzen als Running Example die Berechnung der Shannon-Entropie für eine diskrete Wahrscheinlichkeitsverteilung:
dabei soll calculate_entropy also eine Funktion sein, die die Verteilung (also die ) gegeben bekommt, und einen float zurück gibt, der die Entropie der gegebenen Verteilung ist.
import math
def calculate_entropy(probs):
return -sum(p * math.log2(p) for p in probs if p > 0)
print(calculate_entropy([0.25, 0.25, 0.25, 0.25])) # Output: 2.02.0
Wir fügen nun eine Logik hinzu, die unsere Annahmen über den Parameter probs teilweise verifiziert, ergänzen Type Hints und betten jetzt Doctests ein. Das Modul Doctest sucht interaktive Python-Beispiele in den Docstrings und führt sie aus. Das automatisierte Prüfen von Doctests verhindert, dass diese Code-Beispiele veralten. Da der Docstring z.B. in IDEs oft eingeblendet wird, ist das sehr nützlich.
%%writefile entropy.py
import math
def calculate_entropy(probs: list[float]) -> float:
"""
Berechnet die Shannon-Entropie einer diskreten Verteilung.
>>> calculate_entropy([0.5, 0.5])
1.0
>>> calculate_entropy([1.0, 0.0])
0.0
"""
if not math.isclose(sum(probs), 1.0):
raise ValueError("Wahrscheinlichkeiten müssen sich zu 1 summieren.")
return -sum(p * math.log2(p) for p in probs if p > 0)
Writing entropy.py
# Doctests ausführen/prüfen
!uv run python -m doctest -v entropy.pyTrying:
calculate_entropy([0.5, 0.5])
Expecting:
1.0
ok
Trying:
calculate_entropy([1.0, 0.0])
Expecting:
0.0
**********************************************************************
File "/home/konrad/Sciebo/hhu/eipy-26/eipy-skript/part-tools/entropy.py", line 9, in entropy.calculate_entropy
Failed example:
calculate_entropy([1.0, 0.0])
Expected:
0.0
Got:
-0.0
1 item had no tests:
entropy
**********************************************************************
1 item had failures:
1 of 2 in entropy.calculate_entropy
2 tests in 2 items.
1 passed and 1 failed.
***Test Failed*** 1 failure.
Wir sehen also: unser Doctest hat bereits erste Erfolge gezeigt, nämlich eine falsche Annahme aufgedeckt (dass die Null ohne Vorzeichen ist).
Jetzt müssen wir uns fragen, ob der Test falsch geschrieben ist (also wir dort als Erwartung -0.0 hinschreiben sollten) oder ob wir unseren Code ändern, z.B. durch + 0.0, was gemäß IEEE 754 aus sowohl 0.0 als auch -0.0 einheitlich 0.0 macht.
Hier mag einigermaßen klar sein, was richtig ist; im Allgemeinen sollte man bei jeder verletzten Annahme in Ruhe darüber nachdenken, welche anderen Teile unseres Programm- und Weltmodells von diesem Fehler betroffen sein könnten.
%%writefile entropy.py
import math
def calculate_entropy(probs: list[float]) -> float:
"""
Berechnet die Shannon-Entropie einer diskreten Verteilung.
>>> calculate_entropy([0.5, 0.5])
1.0
>>> calculate_entropy([1.0, 0.0])
0.0
"""
if not math.isclose(sum(probs), 1.0):
raise ValueError("Wahrscheinlichkeiten müssen sich zu 1 summieren.")
return -sum(p * math.log2(p) for p in probs if p > 0) + 0.0Overwriting entropy.py
# Erneut Doctests prüfen
!uv run python -m doctest -v entropy.pyTrying:
calculate_entropy([0.5, 0.5])
Expecting:
1.0
ok
Trying:
calculate_entropy([1.0, 0.0])
Expecting:
0.0
ok
1 item had no tests:
entropy
1 item passed all tests:
2 tests in entropy.calculate_entropy
2 tests in 2 items.
2 passed.
Test passed.
Grundlagen: Unit Tests mit pytest¶
pytest ist das De-facto-Standard-Framework in der Python-Welt. Im Gegensatz zum eingebauten Modul unittest, welches stark objektorientiert ist, glänzt pytest durch eine minimale Syntax (wir nutzen ein simples assert) und aussagekräftige Fehlermeldungen.
Wir erstellen nun eine Datei für unsere Unit Tests, welche die entropy-Funktion isoliert testen. Dabei testen wir auch, ob unter bestimmten Bedingungen ein erwarteter Fehler auftritt (also testen wir nicht nur den Happy Path).
%%writefile test_entropy.py
import pytest
from entropy import calculate_entropy
def test_calculate_entropy_fair_coin():
# Ein typischer Unit Test: Eine spezifische Eingabe testen
assert calculate_entropy([0.5, 0.5]) == 1.0
def test_calculate_entropy_invalid_distribution():
# Hier testen wir, ob die Funktion auch korrekte Fehler wirft!
with pytest.raises(ValueError, match="summieren"):
calculate_entropy([0.5, 0.6])
Writing test_entropy.py
# Tests mit pytest ausführen
!uv run pytest -v test_entropy.py============================= test session starts ==============================
platform linux -- Python 3.13.3, pytest-9.0.3, pluggy-1.6.0 -- /home/konrad/Sciebo/hhu/eipy-26/eipy-skript/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: jaxtyping-0.3.11, cov-5.0.0, hypothesis-6.152.9, hydra-core-1.3.2, Faker-40.23.0, gremlins-1.8.1, typeguard-4.5.2, anyio-4.13.0
collected 2 items
test_entropy.py::test_calculate_entropy_fair_coin PASSED [ 50%]
test_entropy.py::test_calculate_entropy_invalid_distribution PASSED [100%]
============================== 2 passed in 0.09s ===============================
Unter der Haube nutzt pytest das Framework Pluggy, welches eine extrem flexible Plugin-Architektur ermöglicht. Dadurch gibt es hunderte Erweiterungen wie pytest-cov oder pytest-mock (s.u.). Pluggy kommt auch in anderen Testing-Frameworks (wie z.B. tox, s.u.) aber auch in Datenverarbeitungswerkzeugen zum Einsatz.
Testabdeckung (Coverage)¶
Mit dem Plugin pytest-cov können wir messen, wie viel Prozent des Quellcodes während unserer Tests ausgeführt wurden. Eine niedrige Abdeckung ist ein Indikator für fehlende Tests, aber eine hohe Abdeckung keine absolute Garantie für Fehlerfreiheit.
!uv run pytest --cov=entropy test_entropy.py============================= test session starts ==============================
platform linux -- Python 3.13.3, pytest-9.0.3, pluggy-1.6.0
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: jaxtyping-0.3.11, cov-5.0.0, hypothesis-6.152.9, hydra-core-1.3.2, Faker-40.23.0, gremlins-1.8.1, typeguard-4.5.2, anyio-4.13.0
collected 2 items
test_entropy.py .. [100%]
---------- coverage: platform linux, python 3.13.3-final-0 -----------
Name Stmts Miss Cover
--------------------------------
entropy.py 5 0 100%
--------------------------------
TOTAL 5 0 100%
============================== 2 passed in 0.12s ===============================
Mocking: Externe Abhängigkeiten isolieren¶
Gute Unit Tests müssen deterministisch und extrem schnell sein. Wenn unsere Funktion Daten über das Netzwerk lädt oder eine Datenbank abfragt, ersetzen wir diesen Aufruf im Test durch einen Mock. Das Modul unittest.mock ist Teil der Standardbibliothek und wird auch von pytest genutzt.
!uv pip install requestsResolved 5 packages in 87ms
Installed 5 packages in 3ms3.4.7
+ certifi==2026.6.17
+ charset-normalizer==3.4.7
+ idna==3.18
+ requests==2.34.2
+ urllib3==2.7.0
%%writefile entropy_api.py
import requests
from entropy import calculate_entropy
def get_entropy_from_api(url: str) -> float:
"""Simuliert das Abrufen einer Wahrscheinlichkeitsverteilung über ein Netzwerk."""
response = requests.get(url)
probs = response.json().get("probabilities", [])
return calculate_entropy(probs)
Writing entropy_api.py
%%writefile test_entropy_api.py
from unittest.mock import patch
from entropy_api import get_entropy_from_api
# Wir ersetzen 'requests.get' in unserem Modul temporär durch einen Mock
@patch("entropy_api.requests.get")
def test_get_entropy_from_api(mock_get):
# Konfiguration des Mocks: Was soll zurückgegeben werden?
mock_get.return_value.json.return_value = {"probabilities": [0.5, 0.5]}
# Test ausführen (es wird kein echter Netzwerk-Call gemacht!)
result = get_entropy_from_api("http://fake-api.local/data")
# Behauptungen aufstellen
assert result == 1.0
mock_get.assert_called_once_with("http://fake-api.local/data")
Writing test_entropy_api.py
!uv run pytest -v test_entropy_api.py============================= test session starts ==============================
platform linux -- Python 3.13.3, pytest-9.0.3, pluggy-1.6.0 -- /home/konrad/Sciebo/hhu/eipy-26/eipy-skript/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: jaxtyping-0.3.11, cov-5.0.0, hypothesis-6.152.9, hydra-core-1.3.2, Faker-40.23.0, gremlins-1.8.1, typeguard-4.5.2, anyio-4.13.0
collected 1 item
test_entropy_api.py::test_get_entropy_from_api PASSED [100%]
============================== 1 passed in 0.13s ===============================
Oft reichen hartkodierte Werte wie [0.5, 0.5] nicht aus, um realistische API-Antworten oder Datenbankeinträge zu simulieren, insbesondere wenn Metadaten gefordert sind. Das Paket faker generiert realistische, randomisierte Mock-Daten (Namen, URLs, IP-Adressen etc.).
%%writefile data_faker.py
from faker import Faker
fake = Faker()
# Wir generieren dynamisch eine Fake-URL und eine zufällige Verteilung
fake_url = fake.url()
fake_probs = [abs(fake.pyfloat(min_value=0.1, max_value=0.9)) for _ in range(3)]
# Normieren auf 1.0
fake_probs = [p / sum(fake_probs) for p in fake_probs]
print(f"Teste API-Endpunkt: {fake_url}")
print(f"Mit generierter Verteilung: {fake_probs}")Writing data_faker.py
!uv run python data_faker.py
!uv run python data_faker.py
!uv run python data_faker.pyTeste API-Endpunkt: http://snow-torres.biz/
Mit generierter Verteilung: [0.5650466036121851, 0.08917233411751792, 0.345781062270297]
Teste API-Endpunkt: https://www.wilson.com/
Mit generierter Verteilung: [0.3598635496692357, 0.34223561300687505, 0.29790083732388917]
Teste API-Endpunkt: http://www.collins.com/
Mit generierter Verteilung: [0.5781763354772447, 0.11723057081004999, 0.30459309371270543]
Property-Based Testing mit hypothesis¶
In der Mathematik und Statistik prüft ein Hypothesentest Daten auf Signifikanz. In der Programmierung meinen wir mit Paketen wie hypothesis jedoch etwas anderes: das Property-Based Testing.
Anstatt fixe Eingabewerte wie [0.5, 0.5] manuell vorzugeben, definieren wir Invarianten (Properties). Das Framework generiert dann systematisch Hunderte bis Tausende von (auch unüblichen) Eingabewerten.
Für unsere Entropie-Funktion gilt beispielsweise die mathematische Invariante: für jede gültige Verteilung.
%%writefile -a test_entropy.py
from hypothesis import given, strategies as st
# Wir generieren zufällige Listen von positiven Fließkommazahlen (Mindestlänge 1, Maximallänge 10)
@given(st.lists(st.floats(min_value=0.01, max_value=1.0), min_size=1, max_size=10))
def test_entropy_is_non_negative(raw_probs):
# Da unsere Funktion normierte Wahrscheinlichkeiten erwartet, normieren wir die Werte vorher:
total = sum(raw_probs)
probs = [p / total for p in raw_probs]
# Die eigentliche Invariante prüfen
assert calculate_entropy(probs) >= 0.0
Appending to test_entropy.py
# Durch Hypothesis laufen jetzt automatisch viele verschiedene Randfälle durch
!uv run pytest -v test_entropy.py============================= test session starts ==============================
platform linux -- Python 3.13.3, pytest-9.0.3, pluggy-1.6.0 -- /home/konrad/Sciebo/hhu/eipy-26/eipy-skript/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: jaxtyping-0.3.11, cov-5.0.0, hypothesis-6.152.9, hydra-core-1.3.2, Faker-40.23.0, gremlins-1.8.1, typeguard-4.5.2, anyio-4.13.0
collected 3 items
test_entropy.py::test_calculate_entropy_fair_coin PASSED [ 33%]
test_entropy.py::test_calculate_entropy_invalid_distribution PASSED [ 66%]
test_entropy.py::test_entropy_is_non_negative PASSED [100%]
============================== 3 passed in 0.19s ===============================
Symbolic Execution und formale Verifikation: CrossHair¶
Während hypothesis den Zustandsraum stochastisch abtastet (Property-Based Testing), wählt CrossHair einen analytischen Ansatz. Es übersetzt Python-Bytecode in logische Formeln und nutzt einen SMT-Solver (Z3), um mathematisch zu beweisen, ob ein Fehlerzustand erreichbar ist.
CrossHair harmoniert perfekt mit Design by Contract. Wir können Vor- und Nachbedingungen direkt im Code definieren:
%%writefile symbolic_math.py
def integer_square_root(n: int) -> int:
"""
Berechnet die ganzzahlige Quadratwurzel.
pre: n >= 0
post: __return__ ** 2 <= n < (__return__ + 1) ** 2
"""
import math
if(n > 0 and n % 121 == 0):
return 11 # absichtlicher Fehler
else:
return math.isqrt(n)Overwriting symbolic_math.py
!uv run crosshair check --per_condition_timeout=3 symbolic_math.py/home/konrad/Sciebo/hhu/eipy-26/eipy-skript/part-tools/symbolic_math.py:6: error: false when calling integer_square_root(242) (which returns 11)
Mutation Testing: Die Qualität der Tests testen¶
Wer testet eigentlich die Tests? Eine hohe Testabdeckung (Coverage) bedeutet nur, dass der Code ausgeführt wurde, nicht, dass die asserts sinnvoll sind. Mutation Testing verändert den Quellcode (die “Mutanten” – z.B. wird ein > durch ein >= ersetzt oder ein + durch ein -) und führt die Tests erneut aus. Wenn die Tests danach nicht fehlschlagen (der Mutant überlebt), fehlt uns ein spezifischer Testfall. Das Tool mutatest automatisierte dies, wird allerdings nicht mehr aktiv gepflegt. Alternativen sind pytest-gremlins, fest und mutmut, die sich durchaus in ihrer Herangehensweise und damit Performance unterscheiden.
Abschnitt für die pyproject.toml um gremlins zu benutzen:
[tool.pytest.ini_options]
addopts = "--gremlins"
Alternativ kann man direkt beim Aufruf von pytest ein --gremlins angeben.
Man muss ein bisschen aufpassen, denn wenn der Testcode selbst mutiert werden kann, gerade in Kombination mit hypothesis, kann es leicht zu Endlosschleifen kommen. Daher schränkt man ein, welche Dateien mutiert werden sollen (das geht leider nicht per Kommandozeilenoptionen):
[tool.pytest-gremlins]
paths = [ "part-tools/entropy.py" ]
!uv run pytest --gremlins -v test_entropy.py============================= test session starts ==============================
platform linux -- Python 3.13.3, pytest-9.0.3, pluggy-1.6.0 -- /home/konrad/Sciebo/hhu/eipy-26/eipy-skript/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: jaxtyping-0.3.11, cov-5.0.0, hypothesis-6.152.9, hydra-core-1.3.2, Faker-40.23.0, gremlins-1.8.1, typeguard-4.5.2, anyio-4.13.0
collected 3 items
test_entropy.py::test_calculate_entropy_fair_coin PASSED [ 33%]
test_entropy.py::test_calculate_entropy_invalid_distribution PASSED [ 66%]
test_entropy.py::test_entropy_is_non_negative PASSED [100%]Gremlin 1/8: entropy_0617_g001 - running 3/3 tests
Gremlin 2/8: entropy_0617_g002 - running 2/3 tests
Gremlin 3/8: entropy_0617_g003 - running 2/3 tests
Gremlin 4/8: entropy_0617_g004 - running 2/3 tests
Gremlin 5/8: entropy_0617_g005 - running 2/3 tests
Gremlin 6/8: entropy_0617_g006 - running 2/3 tests
Gremlin 7/8: entropy_0617_g007 - running 2/3 tests
Gremlin 8/8: entropy_0617_g008 - running 2/3 tests
======================= pytest-gremlins mutation report ========================
Zapped: 8 gremlins (100%)
Survived: 0 gremlins (0%)
Run with --gremlin-report=html for detailed report.
======================================= =======================================
============================== 3 passed in 2.04s ===============================
Automatisierung und Ökosystem¶
Sobald unsere Test-Suite wächst, wollen wir sie automatisieren und strukturieren.
nox (und tox): Automatisieren das Testen über verschiedene Python-Versionen und getrennte virtuelle Umgebungen hinweg. tox nutzt statische tox.ini-Konfigurationen, während nox reine Python-Skripte (noxfile.py) nutzt.
%%writefile noxfile.py
import nox
# Wir testen gegen zwei Python-Versionen (sofern lokal installiert)
@nox.session(python=["3.12", "3.13"])
def tests(session):
session.install("pytest", "pytest-cov", "hypothesis", "requests")
session.run("pytest", "--cov=entropy")
Writing noxfile.py
# Nox führt nun isolierte Umgebungen aus
!uv run nox -s testsnox > Running session tests-3.12
nox > Creating virtual environment (virtualenv) using python3.12 in .nox/tests-3-12
nox > python -m pip install pytest pytest-cov hypothesis requests
nox > pytest --cov=entropy
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: hypothesis-6.155.7, cov-7.1.0
collected 4 items
test_entropy.py ... [ 75%]
test_entropy_api.py . [100%]
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.12.3-final-0 ________________
Name Stmts Miss Cover
--------------------------------
entropy.py 5 0 100%
--------------------------------
TOTAL 5 0 100%
============================== 4 passed in 1.16s ===============================
nox > Session tests-3.12 was successful in 20 seconds.
nox > Running session tests-3.13
nox > Creating virtual environment (virtualenv) using python3.13 in .nox/tests-3-13
nox > python -m pip install pytest pytest-cov hypothesis requests
nox > pytest --cov=entropy
============================= test session starts ==============================
platform linux -- Python 3.13.3, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/konrad/Sciebo/hhu/eipy-26/eipy-skript
configfile: pyproject.toml
plugins: hypothesis-6.155.7, cov-7.1.0
collected 4 items
test_entropy.py ... [ 75%]
test_entropy_api.py . [100%]
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.3-final-0 ________________
Name Stmts Miss Cover
--------------------------------
entropy.py 5 0 100%
--------------------------------
TOTAL 5 0 100%
============================== 4 passed in 0.84s ===============================
nox > Session tests-3.13 was successful in 20 seconds.
nox > Ran 2 sessions in 40 seconds:
nox > * tests-3.12: success, took 20 seconds
nox > * tests-3.13: success, took 20 seconds
Continuous Integration Testing¶
Eine exemplarische .gitlab-ci.yml um nur Commits zu erlauben, die erfolgreich alle Tests bestehen:
stages:
- test
run_tests:
stage: test
image: python:3.13-slim
script:
- pip install uv
- uv tool install nox
- nox -s testsDer Einsatz von Nox bietet hierbei den Vorteil, dass Testing in einer lokalen Umgebung mit hoher Wahrscheinlichkeit zu den gleichen Ergebnissen kommt wie Testing auf dem Gitlab Runner.
Konfigurationsmanagement mit Hydra¶
Insbesondere im Data Science Bereich und bei Deep Learning Modellen (aber auch in großen Test-Setups) stoßen Kommandozeilenargumente (argparse) schnell an ihre Grenzen. Hydra löst dieses Problem durch hierarchische Komposition von YAML-Dateien. Achtung: Das PyPI-Paket heißt hydra-core, während hydra ein veraltetes anderes Paket ist; das Python-Modul heißt dennoch hydra.
Wir definieren eine Basis-Konfiguration:
%%writefile config.yaml
distribution:
type: "coin_flip"
probs: [0.5, 0.5]
Writing config.yaml
%%writefile run_experiment.py
import hydra
from omegaconf import DictConfig
from entropy import calculate_entropy
# Der Dekorator verknüpft die YAML-Datei automatisch mit dem cfg-Objekt
@hydra.main(version_base=None, config_path=".", config_name="config")
def my_experiment(cfg: DictConfig):
print(f"Starte Experiment: {cfg.distribution.type}")
print(f"Berechnete Entropie: {calculate_entropy(cfg.distribution.probs)}")
if __name__ == "__main__":
my_experiment()
Writing run_experiment.py
Der Clou an Hydra ist, dass wir nun Parameter über die Kommandozeile dynamisch überschreiben können, ohne den Code anzufassen:
# Standard-Ausführung mit Werten aus der config.yaml
!uv run python run_experiment.py
# Dynamisches Überschreiben der Parameter via CLI
!uv run python run_experiment.py distribution.type="loaded_die" distribution.probs="[0.1, 0.9]"Starte Experiment: coin_flip
Berechnete Entropie: 1.0
Starte Experiment: loaded_die
Berechnete Entropie: 0.4689955935892812
Exkurs: Die Rolle von OmegaConf¶
In unserem Skript taucht der Typ DictConfig auf, der aus dem Paket OmegaConf stammt. Hydra nutzt OmegaConf unter der Haube als eigentliche Konfigurations-Engine. OmegaConf bietet weit mehr als nur das simple Einlesen von YAML-Dateien:
Variablen-Interpolation: Werte in der Konfiguration können dynamisch aufeinander verweisen (z. B.
log_name: ${distribution.type}_experiment).Structured Configs: Hier schließt sich der Kreis zur Typisierung. Durch die Kombination von OmegaConf mit Pythons
dataclasseslässt sich ein striktes Schema für die Konfiguration definieren. Falsche Datentypen – etwa die Übergabe eines Strings statt einer Liste fürprobs– werden dadurch direkt beim Start des Programms abgefangen, noch bevor die erste Codezeile des eigentlichen Experiments ausgeführt wird.
Ausblick: End-to-End (E2E) Browser-Testing:¶
Geht es über reine Logik hinaus in Richtung Web-Anwendungen, sind Frameworks wie Selenium oder zunehmend Playwright der moderne Standard, um Klicks und User-Interaktionen in echten Browser-Engines programmatisch zu testen. Das ist sogar außerhalb von Tests interessant, wenn man Browser automatisieren möchte.
Empfehlenswerte Literatur:
PyCQA (Python Code Quality Authority): Das Meta-Projekt hinter Tools wie
autoflake,isortundbandit- GitHub.“Obey the Testing Goat”: Ein hervorragendes, kostenfrei online lesbares Buch über TDD in Python (insb. Webentwicklung): obeythetestinggoat
.com. Software Engineering at Google: Speziell das Kapitel 11 (Testing Overview) von Adam Bender.
# Aufräumen der temporären Dateien
!rm -f config.yaml data_faker.py entropy.py entropy_api.py noxfile.py run_experiment.py symbolic_math.py test_entropy.py test_entropy_api.py tmp_run.py
!rm -rf ./outputs