{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Efficient use of `pytest` fixtures" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Required boilerplate for using `pytest` inside notebooks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's make sure pytest and ipytest packages are installed\n", "# ipytest is required for running pytest inside Jupyter notebooks\n", "import sys\n", "\n", "!{sys.executable} -m pip install pytest\n", "!{sys.executable} -m pip install ipytest\n", "\n", "# These are needed for running pytest inside Jupyter notebooks\n", "import ipytest\n", "\n", "ipytest.autoconfig()\n", "\n", "import pytest" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Parametrizing fixtures\n", "Similarly as you can parametrize test functions with `pytest.mark.parametrize`, you can parametrize fixtures:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "PATHS = [\"/foo/bar.txt\", \"/bar/baz.txt\"]\n", "\n", "\n", "@pytest.fixture(params=PATHS)\n", "def executable(request):\n", " return request.param" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_something_with_executable(executable):\n", " print(executable)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## [`pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/fixture.html#usefixtures)\n", "[`pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/fixture.html#usefixtures) is useful especially when you want to use some fixture in a set of tests but you don't need the return value of the fixture." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "\n", "@pytest.fixture\n", "def my_fixture():\n", " print(\"\\nmy_fixture is used\")\n", " \n", "\n", "@pytest.fixture\n", "def other_fixture():\n", " return \"FOO\"\n", "\n", "\n", "@pytest.mark.usefixtures('my_fixture')\n", "class TestMyStuff:\n", " def test_somestuff(self):\n", " pass\n", " \n", " def test_some_other_stuff(self, other_fixture):\n", " print(f'here we use also other_fixture which returns: {other_fixture}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `pytest` [built-in fixtures](https://docs.pytest.org/en/latest/builtin.html#pytest-api-and-builtin-fixtures)\n", "Here are a couple of examples of the useful built-in fixtures, you can view all available fixtures by running `pytest --fixtures`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [`monkeypatch`](https://docs.pytest.org/en/latest/reference.html#_pytest.monkeypatch.MonkeyPatch)\n", "Built-in [`monkeypatch`](https://docs.pytest.org/en/latest/reference.html#_pytest.monkeypatch.MonkeyPatch) fixture lets you e.g. set environment variables and set/delete attributes of objects. The use cases are similar as with patching/mocking with `unittest.mock.patch`/`unittest.mock.MagicMock` which are part of the Python Standard Library.\n", "\n", "**Monkeypatching environment variables:**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "\n", "def get_env_var_or_none(var_name):\n", " return os.environ.get(var_name, None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_get_env_var_or_none_with_valid_env_var(monkeypatch):\n", " monkeypatch.setenv('MY_ENV_VAR', 'secret')\n", " res = get_env_var_or_none('MY_ENV_VAR')\n", " assert res == 'secret'\n", " \n", "def test_get_env_var_or_none_with_missing_env_var():\n", " res = get_env_var_or_none('NOT_EXISTING')\n", " assert res is None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Monkeypatching attributes:**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class SomeClass:\n", " some_value = \"some value\"\n", "\n", " @staticmethod\n", " def tell_the_truth():\n", " print(\"This is the original truth\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def fake_truth():\n", " print(\"This is modified truth\")\n", "\n", "\n", "@pytest.fixture\n", "def fake_some_class(monkeypatch):\n", " monkeypatch.setattr(\"__main__.SomeClass.some_value\", \"fake value\")\n", " monkeypatch.setattr(\"__main__.SomeClass.tell_the_truth\", fake_truth)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_some_class(fake_some_class):\n", " print(SomeClass.some_value)\n", " SomeClass.tell_the_truth()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [`tmpdir`](https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-fixture)\n", "[`tmpdir`](https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-fixture) fixture provides functionality for creating temporary files and directories." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def word_count_of_txt_file(file_path):\n", " with open(file_path) as f:\n", " content = f.read()\n", " return len(content.split())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_word_count(tmpdir):\n", " test_file = tmpdir.join('test.txt')\n", " test_file.write('This is example content of seven words')\n", " res = word_count_of_txt_file(str(test_file)) # str returns the path\n", " assert res == 7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fixture scope" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@pytest.fixture(scope=\"function\")\n", "def func_fixture():\n", " print(\"\\nfunc\")\n", "\n", "\n", "@pytest.fixture(scope=\"module\")\n", "def module_fixture():\n", " print(\"\\nmodule\")\n", "\n", "\n", "@pytest.fixture(scope=\"session\")\n", "def session_fixture():\n", " print(\"\\nsession\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_something(func_fixture, module_fixture, session_fixture):\n", " pass\n", "\n", "def test_something_else(func_fixture, module_fixture, session_fixture):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup-teardown behaviour" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@pytest.fixture\n", "def some_fixture():\n", " print(\"some_fixture is run now\")\n", " yield \"some magical value\"\n", " print(\"\\nthis will be run after test execution, you can do e.g. some clean up here\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_something(some_fixture):\n", " print('running test_something')\n", " assert some_fixture == 'some magical value'\n", " print('test ends here')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using fixtures automatically" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@pytest.fixture(autouse=True)\n", "def my_fixture():\n", " print(\"\\nusing my_fixture\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%ipytest -s\n", "\n", "def test_1():\n", " pass\n", " \n", "def test_2():\n", " pass" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "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.0" } }, "nbformat": 4, "nbformat_minor": 2 }