# Tutorial 5: Automated Testing / Linting ASE WS 2023/24 ## Ziele - Überblick Tools für Testing und Linting (Python) - Praktische Anwendung der Tools - Überblick Build Automation / Dokumentation / Testing - Praktische Anwendung GitLab CI ## Setup Wir arbeiten dieses Mal mit einer lokalen Python-Installation Alternativ kann man sich auch wie beim letzten Mal als Nutzer `root` auf einem 'disposable root server' [via segfault.net](https://blog.thc.org/disposable-root-servers) einloggen. ssh root@segfault.net # Password is 'segfault' **Achtung:** Der Server ist nicht sicher - keine vertraulichen Infos/Passwörter dort eintippen ## Virtual Environments in Python Es bietet sich an, für jedes Python-Projekt ein eigenes *virtual environment* zu verwenden. Dazu wird in einem Unterverzeichnis des Projekts eine separate Python-Installation eingerichtet. Ein Aktivierungs-Script setzt dann für die aktuelle Shell-Session die Pfade und Umgebungsvariablen so, dass nicht mehr die systemweite Python-Installation verwendet wird, sondern die im Unterverzeichnis installierte. Wenn man mittels `pip` neue Pakete installiert, werden diese auch nur im *virtual environment (venv)* installiert. Das folgende Code-Beispiel installiert das venv-Modul, richtet ein venv im Unterverzeichnis ".env" ein und aktiviert dieses für den Rest der Session. $ sudo apt install python3-venv [...] $ mkdir myproject; cd myproject $ python3 -m venv .myenv $ source .myenv/bin/activate Alle folgenden Befehle verwenden jetzt die Python-Installation in `.myenv`: $ which python3 /home/me/myproject/.myenv/bin/python3 ## Meyer's Triangle Problem Glenford J. Meyers, Corey Sandler, Tom Badgett (2015) [The Art of Software Testing](https://www.wiley.com/en-us/The+Art+of+Software+Testing%2C+3rd+Edition-p-9781119202486), John Wiley & Son, 1978/2004/2015 > We want you to write a set of test cases—specific sets of data—to properly test a relatively simple program. Create a set of test data for the program—data the program must handle correctly to be considered a successful program. Here’s a description of the program: * The program reads three integer values from an input dialog. * The three values represent the lengths of the sides of a triangle. * The program displays a message that states whether the triangle is scalene (ungleichschenklig, ungleichseitig), isosceles (gleichschenklig), or equilateral (gleichseitig). ## Let's do it by hand Erstelle eine Datei `triangle.py` oder klone [das Git Repo](https://git.uni-regensburg.de/ase23ws/ci-cd-example): def triangle_shape(x, y, z): return None def triangle_circumference(x, y, z): return None def test_triangle(): # your code if __name__ == "__main__": import sys if sys.argv[1] == "--test": test_triangle() else: print(triangle_shape(sys.argv[1], sys.argv[1], sys.argv[3])) (Achtung: es sind Syntax- und Logikfehler im Code) - Bringe die Datei in einen lauffähigen Zustand. - Wie können wir den Code robuster und testbarer machen? ### Linting Linter überprüfen den Code statisch auf Syntaxfehler und Verstöße gegen Style-Guides. Hilfreich: - pep8 - flake8 - mypy Überprüfe den Code mit den Lintern und behebe Probleme ### Testing Ergänze test_triangle um notwendige Tests. Verwende dazu `assert()` (siehe dieses Tutorial: https://realpython.com/python-assert-statement/) ## pytest *pytest* ist ein Test-Framework für Python * Tests landen in einer separaten Datei, meist auch in einem separaten Unterverzeichnis: `mycode.py` -> `tests/test_mycode.py` * `pytest -v tests/test_mycode.py` führt alle Tests in der Datei aus. * Jeder Test wird als Funktion geschrieben: `def test_isoceles():` * Exception -> fail **Beispiel:** def test_addition(): assert(1+1==2) Wenn eine Exception erwartet wird: def test_fails(): with pytest.raises(Exception): x = 1 / 0 Decorators sorgen dafür, dass Tests übersprungen werden: @pytest.mark.skip("not yet finished") def test_something(): pass @pytest.mark.skipif(db is None, reason = "No database available") def test_db(): assert(db.status == 3) Fixtures erlauben es, benötigten Code vorher auszuführen @pytest.fixture def database(): db = DataBase(123) yield db db.close() def test_insert(database): database.insert(1) Implementiere die notwendigen Tests und führe sie aus. # Weitere Ressourcen * [atinfo/awesome-test-automation](https://github.com/atinfo/awesome-test-automation) - Sammlung von Ressourcen für verschiedene Programmiersprachen * [NoriSte/ui-testing-best-practices](https://github.com/NoriSte/ui-testing-best-practices) * [Selenium](https://www.selenium.dev/) - Automatisierung von Interaktion mit Webseiten - nützlich für UI-Tests * [Puppetry](https://puppetry.app/) - frontend für Google's [Puppeteer](https://pptr.dev/)-Framework zum automatisierten Testen von Web-UIs über Chromium