From 5d37a1efb2130ddf65cff1cf2968f8fd042f5e01 Mon Sep 17 00:00:00 2001 From: thomasabishop Date: Thu, 8 Jan 2026 19:44:37 +0000 Subject: [PATCH] additional notes on python testing --- zk/Pytest_fixtures.md | 88 +++++++++++++++++++++++++++++++++++++++ zk/Testing_Python_code.md | 14 +++---- 2 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 zk/Pytest_fixtures.md diff --git a/zk/Pytest_fixtures.md b/zk/Pytest_fixtures.md new file mode 100644 index 0000000..c9cb8ff --- /dev/null +++ b/zk/Pytest_fixtures.md @@ -0,0 +1,88 @@ +--- +tags: [python, pytest] +--- + +Fixtures are a way to provide data or state to your tests. Typically, although +not exclusively, for processes that you want to reuse accross different `test_` +functions. They are defined at the "arrange" stage of testing. + +## Example: testing class methods under test + +```py +class Person: + def __init__(self, name): + self.name = name + + def print_name(self): + return f"Your name is {self.name}" +``` + +To create a fixture of this class: + +```py +@pytest.fixture +def my_person(): + person = new Person('Thomas') + return person.name() + +test_print_name(my_person): + assert(my_person) == 'Thomas' + +``` + +> Note we define our fixture and then inject it into our test function. +> Functions request fixtures they require by declaring them as arguments. + +Alternatively you could just return the class from the fixture and test the +methods individually within the test function. + +## Integrating with `@patch` to provide mock fixtures + +Say we wanted to mock reading a JSON file. + +We could create a fixture: + +```py + +@pytest fixture +def raw_json(): + return json.dumps({"key": "value"}) +``` + +And pass it in to the mock of the global `open` method: + +```py +from unittest.mock import mock_open, patch + +def test_json_parsing(raw_json): + with patch("builtins.open", mock_open(read_data=raw_json)) + # Then assert etc +``` + +This fixture could then be re-used to test any other method that relies on +incoming JSON from a file. + +## Example: setup and teardown - before each and after each + +Fixtures can be leveraged to setup 'before each...' and 'after each...' logic: + +```py +@pytest.fixture(scope="function") +def setup(): + os.environ["POCKET_LAMBDA_ENDPOINT"] = "https://some_endpoint.com/{article_type}" + yield + del os.environ["POCKET_LAMBDA_ENDPOINT"] +``` + +Here `yield` is a placeholder for the tests that run in between the setup and +teardown. + +We use `scope="function"` to signal that this will run before/after any tests +that inject the fixture. + +Then to specify the units that will run as the `yield` we just inject `setup`: + +```py +def some_function(setup_function): + pass +``` diff --git a/zk/Testing_Python_code.md b/zk/Testing_Python_code.md index 38a8de5..b92f4c6 100644 --- a/zk/Testing_Python_code.md +++ b/zk/Testing_Python_code.md @@ -1,18 +1,16 @@ --- -tags: [python, testing] +tags: [python, testing, pytest] --- -# Testing Python code - ## `pytest` Pytest is the most popular testing library for Python. It is not included with the Python standard library so it must be installed with -[pip](Python_package_management.md). -While it does not include a declaration library, it is robust enough to handle -most scenarios having a rich and expressive set of constructs and decorators -that let you declare what your tests should do, under what conditions they -should run, and how they should interact with the rest of your system. +[pip](Python_package_management.md). While it does not include a declaration +library, it is robust enough to handle most scenarios having a rich and +expressive set of constructs and decorators that let you declare what your tests +should do, under what conditions they should run, and how they should interact +with the rest of your system. ### Using `pytest`