Test DRIVEN DEVELOPMENT
für
Datenwissenschaftler
Was ist das Schwierigste Problem in der IT?
Stabilität!
Warum?
Weil alles leicht zu ändern ist
Lösung 1
Ein wirklich Strikter Compiler
ADA
- Programmiersprache benannt nach Ada Lovelace
- Sie war der erste Programmierer überhaupt
- ADA wird verwendet (hoffentlich!?) für Militär, Raketen, Atomkraftwerke etc.
type Day_type is range 1 .. 31; type Month_type is range 1 .. 12; type Year_type is range 1800 .. 2100; type Hours is mod 24; type Weekday is (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday); type Date is record Day : Day_type; Month : Month_type; Year : Year_type; end record;
ADA Beispiel
ADA NAchteile
-
extrem komplexe Sprache
-
extrem strenge Regeln
-
extrem aufwändiges, langsames und teures Programmieren
Lösung 2
TEST Driven Development
- Beck, K. (2003): Test-Driven Development by Example
- Automatisierte Tests
- Schwerpunkt auf Unittests
- Modernere Sichtweise
- Martin (2008): Clean Code
Vokabeln
- Unit Tests
- schnell, in-memory
- keine externen Abhängigkeiten
- Integration Tests
- in-memory
- Abhängigkeiten wie DBs in-memory
- End-to-End Tests
- möglichst nahe am Produktionssystem
- Docker-Container
- Abhängigkeiten werden mitgetestet
Perfekte Welt
- Test programmieren bevor man die Funktionalität programmiert
- Test schlägt zunächst fehl
- Solange programmieren, bis der Test "grün" ist
- Sämtlicher Code ist auf diese Weise programmiert und damit auch getestet
REALE Welt
- Legacy Code ohne Tests
- Test als "Dokumentation" im Nachhinein hinzufügen
- Tests um Bugs zu reproduzieren
- Code-Abdeckung 0-50%
DATA Science Welt
- Python Spaghetti-Code mit vielen globalen Variablen
- verteilt auf mehrere Jupyter Notebooks
- Nicht reproduzierbare Umgebung
- keine Tests
- keine Dokumentation
- kein wirkliches Versionsmanagment
- Jupyter Notebook Format erlaubt keine richtigen Diffs
- Versionsmanagment von Daten scheitert oft an der Datenmenge, die zu groß ist um sie mit git zu versionieren
Schritte zur Verbesserung
- Benutze git (auch für Daten bis 100MB)
- Benutze virtualenv, requirements.txt, pip, pipenv oder poetry um die Umgebung reproduzierbar wiederherstellen zu können
- Bewege Code aus Jupyter Notebooks in richtige Python-Dateien
- Keine def und class Definitionen in Jupyter Notebooks!
- Nutze Jupyter Notebooks als "manuelle" Tests!
- Nutze Python Skripts statt Jupyter Notebooks für wiederkehrende Aufgaben (z.B. regelmäßige Datenbankupdates, Webseiten-Scrapings etc.)
Pytest
- Installation
-
pipenv install --dev pytest pytest-mock
-
- Nutzung
-
pytest -s
-
= test session starts= platform darwin -- Python 3.6.5, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 rootdir: <PATH>, inifile: plugins: mock-1.10.1 collected 0 items = no tests ran in 0.01 seconds =========
-
- Standardmäßig sucht pytest nach
-
Dateien mit test_ *.py oder *_test.py
- Funktionen mit Namen test*
- Klassen mit Namen Test*
-
-
Erstes Beispiel
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
$ pytest
= test session starts=
...
collected 1 item
test_sample.py F [100%]
= FAILURES =
_ test_answer _
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:5: AssertionError
= 1 failed in 0.12 seconds =
Eine TESTKlasse
from elasticsearch import Elasticsearch, RequestError import elasticwrapper as ew import pytest # Test needs real elasticsearch (Integration/End2end-Test) class TestApp(object): @classmethod def setup_class(cls): es_host = "http://35.242.247.72:9200/" cls.es = Elasticsearch(es_host, timeout=300) @classmethod def teardown_class(cls): print("Tests stopped!") def test_match_field_names_None(self): with pytest.raises(RequestError): result = ew.match_field_names(None, es=self.es, index="meta")
Mocking
import actions
# Test works without Elasticsearch (Unit test)
class TestApp(object):
def test_get_index(self, mocker):
mock_es = mocker.MagicMock()
mock_es.configure_mock(**{"indices.exists_alias.return_value": False})
index = actions.get_index("product_finder", mock_es)
assert index is None
mock_es.indices.exists_alias.assert_called_once()
# check that we don't call elasticsearch if we have the index name in cache
mock_es.reset_mock()
index = actions.get_index("product_finder", mock_es, cache={"product_finder": "cached_name"})
assert index == "cached_name"
mock_es.indices.exists_alias.assert_not_called()
Referenzen
- Python Standard Bibliothek Dokumentation
- Pytest Dokumentation
- Pytest-mock
- PyCharm Unterstützung für pytest
ENDE
Test Driven Development
By andimai
Test Driven Development
für Datenwissenschaftler
- 160