{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Mock\n", "\n", "[Mock-Objekte](https://de.wikipedia.org/wiki/Mock-Objekt) fördern Tests, die auf dem Verhalten von Objekten basieren. Die Python-Bibliothek [mock](https://docs.python.org/3/library/unittest.mock.html) ermöglicht euch, Teile des zu testenden Systems durch Scheinobjekte zu ersetzen und Aussagen über deren Verwendung zu treffen." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Installation\n", "\n", "[mock](https://docs.python.org/3/library/unittest.mock.html) ist seit Python 3.3 in der Python-Standardbibliothek enthalten. Für ältere Versionen von Python könnt ihr sie installieren mit:\n", "\n", "```bash\n", "$ bin/python -m pip install mock\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Beispiel\n", "\n", "In unserem Beispiel wollen wir prüfen, ob die Arbeitstage von Montag bis Freitag korrekt ermittelt werden." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Zunächst importieren wir `datetime` und `Mock`:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from datetime import datetime\n", "from unittest.mock import Mock" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "2. Dann definieren wir zwei Testtage:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "monday = datetime(year=2021, month=10, day=11)\n", "saturday = datetime(year=2021, month=10, day=16)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "3. Nun definieren wir eine Methode zur Überprüfung der Arbeitstage, wobei die `datetime`-Bibliothek von Python Montage als `0` und Sonntage als `6` behandelt:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def is_workingday():\n", " today = datetime.today()\n", " return (0 <= today.weekday() < 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "4. Dann mocken wir `datetime`:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "datetime = Mock()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "5. Schließlich testen wir unsere beiden Mock-Objekte:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "datetime.today.return_value = monday\n", "assert is_workingday()" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "datetime.today.return_value = saturday\n", "assert not is_workingday()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "ename": "AssertionError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAssertionError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[7], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m datetime\u001b[38;5;241m.\u001b[39mtoday\u001b[38;5;241m.\u001b[39mreturn_value \u001b[38;5;241m=\u001b[39m monday\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m is_workingday()\n", "\u001b[0;31mAssertionError\u001b[0m: " ] } ], "source": [ "datetime.today.return_value = monday\n", "assert not is_workingday()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Siehe auch:**\n", "\n", "* [Introducing time-machine, a New Python Library for Mocking the Current Time](https://adamj.eu/tech/2020/06/03/introducing-time-machine/)\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `mock.ANY`\n", "\n", "Mit [mock.ANY](https://docs.python.org/3/library/unittest.mock.html#any) könnt ihr prüfen, ob ein Wert überhaupt vorhanden ist, ohne einen genauen Wert prüfen zu müssen:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "from unittest.mock import ANY\n", "\n", "\n", "mock = Mock(return_value=None)\n", "mock(\"foo\", bar=object())\n", "mock.assert_called_once_with(\"foo\", bar=ANY) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Hinweis:**\n", "\n", "In [test_report.py](https://github.com/openstack/zun/blob/917868f5fe02ff419fd35c5d9332f45a064ed385/zun/tests/unit/scheduler/client/test_report.py) des OpenStack Containerdienstes Zun findet ihr weitere praktische Beispiele für `ANY`.\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `patch`-Dekorator\n", "\n", "Um Mock-Klassen oder Objekte zu erzeugen, kann der `patch`-Dekorator verwendet werden. In den folgenden Beispielen wird die Ausgabe von `os.listdir` gemockt. Dazu muss die Datei `example.txt` nicht im Verzeichnis vorhanden sein:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "from unittest import mock" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "@mock.patch(\"os.listdir\", mock.MagicMock(return_value=\"example.txt\"))\n", "def test_listdir():\n", " assert \"example.txt\" == os.listdir()\n", "\n", "\n", "test_listdir()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternativ kann der Rückgabewert auch separat definiert werden:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "@mock.patch(\"os.listdir\")\n", "def test_listdir(mock_listdir):\n", " mock_listdir.return_value = \"example.txt\"\n", " assert \"example.txt\" == os.listdir()\n", "\n", "\n", "test_listdir()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Hinweis:**\n", "\n", "Mit [responses](https://github.com/getsentry/responses) könnt ihr Mock-Objekte für die [httpx](https://www.python4data.science/de/latest/data-processing/httpx/index.html)-Bibliothek erstellen.\n", "
" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3.11 Kernel", "language": "python", "name": "python311" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 2 }