From 5c61ac6e7b987410406a6de8801400c53ef4d4e1 Mon Sep 17 00:00:00 2001 From: mwychung Date: Fri, 29 Nov 2024 16:39:22 +0800 Subject: [PATCH 1/9] Re-add modified types documentation to types.rst --- doc/en/how-to/types.rst | 235 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 doc/en/how-to/types.rst diff --git a/doc/en/how-to/types.rst b/doc/en/how-to/types.rst new file mode 100644 index 00000000000..65a74476896 --- /dev/null +++ b/doc/en/how-to/types.rst @@ -0,0 +1,235 @@ +.. _types: + +Enhancing Type Annotations with Pytest +====================================== + +This page assumes the reader is familiar with Python's typing system and its advantages. +For more information, refer to `Python's Typing Documentation `_. + +Why Type Tests? +--------------- + +Typing tests in pytest provide unique advantages distinct from typing production code. Typed tests emphasize robustness in edge cases and diverse datasets. +Type annotations provide an additional layer of validation, reducing the risk of runtime failures. + +- **Test Clarity:** Clearly defines expected inputs and outputs, improving readability, especially in complex or parameterized tests. + +- **Type Safety:** Helps catch mistakes in test data early, reducing runtime errors. + +- **Refactoring Support:** Serves as in-code documentation, clarifying data expectations and minimizing errors during test suite modifications. + +These benefits make typed tests a powerful tool for maintaining clarity, consistency, and safety throughout the testing process. + +Typing Test Functions +--------------------- +By adding type annotations to test functions, tests are easier to read and understand. +This is particularly helpful when developers need to refactor code or revisit tests after some time. + +For example: + +.. code-block:: python + + import pytest + + + def add(a: int, b: int) -> int: + return a + b + + + def test_add() -> None: + result = add(2, 3) + assert result == 5 + +- Here, `test_add` is annotated with `-> None`, as it does not return a value. +- While `-> None` typing may seem unnecessary, it ensures type checkers validate the function and helps identifying potential issues during refactoring. + + +Typing Fixtures +--------------- +Fixtures in pytest helps set up data or provides resources needed for tests. +Adding type annotations to fixtures makes it clear what data they return, which helps with debugging and readability. + +* Basic Fixture Typing + +.. code-block:: python + + import pytest + + + @pytest.fixture + def sample_fixture() -> int: + return 38 + + + def test_sample_fixture(sample_fixture: int) -> None: + assert sample_fixture == 38 + +- Here, `sample_fixture()` is typed to return an `int`. This ensures consistency and helps identify mismatch types during refactoring. + + +* Typing Fixtures with Lists and Dictionaries +This example shows how to use List and Dict types in pytest. + +.. code-block:: python + + from typing import List, Dict + import pytest + + + @pytest.fixture + def sample_list() -> List[int]: + return [5, 10, 15] + + + def test_sample_list(sample_list: List[int]) -> None: + assert sum(sample_list) == 30 + + + @pytest.fixture + def sample_dict() -> Dict[str, int]: + return {"a": 50, "b": 100} + + + def test_sample_dict(sample_dict: Dict[str, int]) -> None: + assert sample_dict["a"] == 50 + +- Annotating fixtures with types like List[int] and Dict[str, int] ensures data consistency and helps prevent runtime errors when performing operations. +This ensures that only `int` values are allowed in the list and that `str` keys map to `int` values in the dictionary, helping avoid type-related issues. + +Typing Parameterized Tests +-------------------------- +With `@pytest.mark.parametrize`, adding typing annotations to the input parameters reinforce type safety and reduce errors with multiple data sets. + +For example, you are testing if adding 1 to `input_value` results in `expected_output` for each set of arguments. + +.. code-block:: python + + import pytest + + + @pytest.mark.parametrize("input_value, expected_output", [(1, 2), (5, 6), (10, 11)]) + def test_increment(input_value: int, expected_output: int) -> None: + assert input_value + 1 == expected_output + +- Here, typing clarifies that both `input_value` and `expected_output` are expected as integers, promoting consistency. +While parameterized tests can involve varied data types and that annotations simplify maintenance when datasets grow. + + +Typing for Monkeypatching +-------------------------- +Monkeypatching modifies functions or environment variables during runtime. +Adding typing, such as `monkeypatch: pytest.MonkeyPatch`, clarifies the expected patching behaviour and reduces the risk of errors. + + * Example of Typing Monkeypatching Environment Variables + +This example is based on the pytest documentation for `Monkeypatching `_, with the addition of typing annotations. + +.. code-block:: python + + # contents of our original code file e.g. code.py + import pytest + import os + from typing import Optional + + + def get_os_user_lower() -> str: + """Simple retrieval function. Returns lowercase USER or raises OSError.""" + username: Optional[str] = os.getenv("USER") + + if username is None: + raise OSError("USER environment is not set.") + + return username.lower() + + + # contents of our test file e.g. test_code.py + @pytest.fixture + def mock_env_user(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("USER", "TestingUser") + + + @pytest.fixture + def mock_env_missing(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("USER", raising=False) + + + def test_upper_to_lower(mock_env_user: None) -> None: + assert get_os_user_lower() == "testinguser" + + + def test_raise_exception(mock_env_missing: None) -> None: + with pytest.raises(OSError): + _ = get_os_user_lower() + +Here: + +- **`username: Optional[str]`:** Indicates the variable `username` may either be a string or `None`. +- **`get_os_user_lower() -> str`:** Specifies this function will return a string, providing explicit return value type. +- **`monkeypatch` fixture is typed as `pytest.MonkeyPatch`:** Shows that it will provide an object for patching environment variables during the test. This clarifies the intended use of the fixture and helps developers to use it correctly. +- **Fixture return `-> None`, like `mock_env_user`:** Specifies they do not return any value, but instead modify the test environment. + +Typing annotations can also be extended to `monkeypatch` usage in pytest for class methods, instance attributes, or standalone functions. +This enhances type safety and clarity when patching the test environment. + + +Typing Temporary Directories and Paths +-------------------------------------- +Temporary directories and paths are commonly used in pytest to create isolated environments for testing file and directory operations. +The `tmp_path` and `tmpdir` fixtures provide these capabilities. +Adding typing annotations enhances clarity about the types of objects these fixtures return, which is particularly useful when performing file operations. +It also prevents misuse of monkeypatch by clarifies its API and expected inputs. + +Below examples are based on the pytest documentation for `Temporary Directories and Files in tests `, with the addition of typing annotations. + + * Typing with `tmp_path` for File Creation + +.. code-block:: python + + import pytest + from pathlib import Path + + # content of test_tmp_path.py + CONTENT = "content" + + + def test_create_file(tmp_path: Path) -> None: + d = tmp_path / "sub" + d.mkdir() + p = d / "hello.txt" + p.write_text(CONTENT, encoding="utf-8") + assert p.read_text(encoding="utf-8") == CONTENT + assert len(list(tmp_path.iterdir())) == 1 + +- Typing `tmp_path: Path` explicitly defines it as a Path object, improving code readability and catching type issues early. + + * Typing with `tmp_path_factory` fixture for creating temporary files during a session + +.. code-block:: python + + # contents of conftest.py + import pytest + from pathlib import Path + + + @pytest.fixture(scope="session") + def image_file(tmp_path_factory: pytest.TempPathFactory) -> Path: + img = compute_expensive_image() + fn: Path = tmp_path_factory.mktemp("data") / "img.png" + img.save(fn) + return fn + + + # contents of test_image.py + def test_histogram(image_file: Path) -> None: + img = load_image(image_file) + # compute and test histogram + +- **`tmp_path_factory: pytest.TempPathFactory`:** Indicates that `tmp_path_factory` is an instance of pytest’s `TempPathFactory`, responsible for creating temporary directories and paths during testing. +- **`fn: Path`:** Identifies that `fn` is a `Path` object, emphasizing its role as a file path and clarifying the expected file operations. +- ** Return type `-> Path`:** Specifies the fixture returns a `Path` object, clarifying its expected structure. +- **`image_file: Path`:** Defines `image_file` as a Path object, ensuring compatibility with `load_image`. + +Conclusion +---------- +Incorporating typing into pytest tests enhances **clarity**, improves **debugging** and **maintenance**, and ensures **type safety**. +These practices lead to a **robust**, **readable**, and **easily maintainable** test suite that is better equipped to handle future changes with minimal risk of errors. From 8fdf89f9d842bcfb0f2fd6b801c38fe8bc1f552e Mon Sep 17 00:00:00 2001 From: mwychung Date: Fri, 29 Nov 2024 16:40:56 +0800 Subject: [PATCH 2/9] Update AUTHORS file --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index ab72f4b7ed7..95e6b13f11e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -265,6 +265,7 @@ lovetheguitar Lukas Bednar Luke Murphy Maciek Fijalkowski +Maggie Chung Maho Maik Figura Mandeep Bhutani From 335327c7d9d2813613d799f9e45e9455f186bb38 Mon Sep 17 00:00:00 2001 From: mwychung Date: Fri, 29 Nov 2024 16:42:48 +0800 Subject: [PATCH 3/9] Added changelog entry for #12842 --- changelog/12842.doc.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/12842.doc.rst diff --git a/changelog/12842.doc.rst b/changelog/12842.doc.rst new file mode 100644 index 00000000000..22d351fc3d8 --- /dev/null +++ b/changelog/12842.doc.rst @@ -0,0 +1,2 @@ +Added dedicated page about using types with pytest +See :ref:`types` for detailed usage. From c105afb1f48e00f09ae63b1152f2f7023d11ccb8 Mon Sep 17 00:00:00 2001 From: mwychung Date: Fri, 29 Nov 2024 17:07:31 +0800 Subject: [PATCH 4/9] Modified formatting of types documentation to types.rst --- doc/en/how-to/types.rst | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/doc/en/how-to/types.rst b/doc/en/how-to/types.rst index 65a74476896..227ffa8f86c 100644 --- a/doc/en/how-to/types.rst +++ b/doc/en/how-to/types.rst @@ -40,8 +40,8 @@ For example: result = add(2, 3) assert result == 5 -- Here, `test_add` is annotated with `-> None`, as it does not return a value. -- While `-> None` typing may seem unnecessary, it ensures type checkers validate the function and helps identifying potential issues during refactoring. +Here, `test_add` is annotated with `-> None`, as it does not return a value. +While `-> None` typing may seem unnecessary, it ensures type checkers validate the function and helps identifying potential issues during refactoring. Typing Fixtures @@ -64,7 +64,7 @@ Adding type annotations to fixtures makes it clear what data they return, which def test_sample_fixture(sample_fixture: int) -> None: assert sample_fixture == 38 -- Here, `sample_fixture()` is typed to return an `int`. This ensures consistency and helps identify mismatch types during refactoring. +Here, `sample_fixture()` is typed to return an `int`. This ensures consistency and helps identify mismatch types during refactoring. * Typing Fixtures with Lists and Dictionaries @@ -93,7 +93,7 @@ This example shows how to use List and Dict types in pytest. def test_sample_dict(sample_dict: Dict[str, int]) -> None: assert sample_dict["a"] == 50 -- Annotating fixtures with types like List[int] and Dict[str, int] ensures data consistency and helps prevent runtime errors when performing operations. +Annotating fixtures with types like List[int] and Dict[str, int] ensures data consistency and helps prevent runtime errors when performing operations. This ensures that only `int` values are allowed in the list and that `str` keys map to `int` values in the dictionary, helping avoid type-related issues. Typing Parameterized Tests @@ -111,7 +111,7 @@ For example, you are testing if adding 1 to `input_value` results in `expected_o def test_increment(input_value: int, expected_output: int) -> None: assert input_value + 1 == expected_output -- Here, typing clarifies that both `input_value` and `expected_output` are expected as integers, promoting consistency. +Here, typing clarifies that both `input_value` and `expected_output` are expected as integers, promoting consistency. While parameterized tests can involve varied data types and that annotations simplify maintenance when datasets grow. @@ -120,7 +120,7 @@ Typing for Monkeypatching Monkeypatching modifies functions or environment variables during runtime. Adding typing, such as `monkeypatch: pytest.MonkeyPatch`, clarifies the expected patching behaviour and reduces the risk of errors. - * Example of Typing Monkeypatching Environment Variables +* Example of Typing Monkeypatching Environment Variables This example is based on the pytest documentation for `Monkeypatching `_, with the addition of typing annotations. @@ -163,10 +163,10 @@ This example is based on the pytest documentation for `Monkeypatching str`:** Specifies this function will return a string, providing explicit return value type. -- **`monkeypatch` fixture is typed as `pytest.MonkeyPatch`:** Shows that it will provide an object for patching environment variables during the test. This clarifies the intended use of the fixture and helps developers to use it correctly. -- **Fixture return `-> None`, like `mock_env_user`:** Specifies they do not return any value, but instead modify the test environment. +- **username: Optional[str]:** Indicates the variable `username` may either be a string or `None`. +- **get_os_user_lower() -> str:** Specifies this function will return a string, providing explicit return value type. +- **monkeypatch fixture is typed as pytest.MonkeyPatch:** Shows that it will provide an object for patching environment variables during the test. This clarifies the intended use of the fixture and helps developers to use it correctly. +- **Fixture return -> None, like mock_env_user:** Specifies they do not return any value, but instead modify the test environment. Typing annotations can also be extended to `monkeypatch` usage in pytest for class methods, instance attributes, or standalone functions. This enhances type safety and clarity when patching the test environment. @@ -179,9 +179,9 @@ The `tmp_path` and `tmpdir` fixtures provide these capabilities. Adding typing annotations enhances clarity about the types of objects these fixtures return, which is particularly useful when performing file operations. It also prevents misuse of monkeypatch by clarifies its API and expected inputs. -Below examples are based on the pytest documentation for `Temporary Directories and Files in tests `, with the addition of typing annotations. +Below examples are based on the pytest documentation for `Temporary Directories and Files in tests `_, with the addition of typing annotations. - * Typing with `tmp_path` for File Creation +* Typing with `tmp_path` for File Creation .. code-block:: python @@ -200,9 +200,9 @@ Below examples are based on the pytest documentation for `Temporary Directories assert p.read_text(encoding="utf-8") == CONTENT assert len(list(tmp_path.iterdir())) == 1 -- Typing `tmp_path: Path` explicitly defines it as a Path object, improving code readability and catching type issues early. +Typing `tmp_path: Path` explicitly defines it as a Path object, improving code readability and catching type issues early. - * Typing with `tmp_path_factory` fixture for creating temporary files during a session +* Typing with `tmp_path_factory` fixture for creating temporary files during a session .. code-block:: python @@ -224,10 +224,11 @@ Below examples are based on the pytest documentation for `Temporary Directories img = load_image(image_file) # compute and test histogram -- **`tmp_path_factory: pytest.TempPathFactory`:** Indicates that `tmp_path_factory` is an instance of pytest’s `TempPathFactory`, responsible for creating temporary directories and paths during testing. -- **`fn: Path`:** Identifies that `fn` is a `Path` object, emphasizing its role as a file path and clarifying the expected file operations. -- ** Return type `-> Path`:** Specifies the fixture returns a `Path` object, clarifying its expected structure. -- **`image_file: Path`:** Defines `image_file` as a Path object, ensuring compatibility with `load_image`. +Here: +- **tmp_path_factory: pytest.TempPathFactory:** Indicates that `tmp_path_factory` is an instance of pytest’s `TempPathFactory`, responsible for creating temporary directories and paths during testing. +- **fn: Path:** Identifies that `fn` is a `Path` object, emphasizing its role as a file path and clarifying the expected file operations. +- **Return type -> Path:** Specifies the fixture returns a `Path` object, clarifying its expected structure. +- **image_file: Path:** Defines `image_file` as a Path object, ensuring compatibility with `load_image`. Conclusion ---------- From 054ca5cd130cbc7a14f4019a7625257853d8537a Mon Sep 17 00:00:00 2001 From: mwychung Date: Fri, 29 Nov 2024 19:56:33 +0800 Subject: [PATCH 5/9] Updated the section of Typing Temporary Directories and Paths --- doc/en/how-to/types.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/how-to/types.rst b/doc/en/how-to/types.rst index 227ffa8f86c..e2ab22b25c4 100644 --- a/doc/en/how-to/types.rst +++ b/doc/en/how-to/types.rst @@ -116,7 +116,7 @@ While parameterized tests can involve varied data types and that annotations sim Typing for Monkeypatching --------------------------- +------------------------- Monkeypatching modifies functions or environment variables during runtime. Adding typing, such as `monkeypatch: pytest.MonkeyPatch`, clarifies the expected patching behaviour and reduces the risk of errors. @@ -177,7 +177,6 @@ Typing Temporary Directories and Paths Temporary directories and paths are commonly used in pytest to create isolated environments for testing file and directory operations. The `tmp_path` and `tmpdir` fixtures provide these capabilities. Adding typing annotations enhances clarity about the types of objects these fixtures return, which is particularly useful when performing file operations. -It also prevents misuse of monkeypatch by clarifies its API and expected inputs. Below examples are based on the pytest documentation for `Temporary Directories and Files in tests `_, with the addition of typing annotations. @@ -225,6 +224,7 @@ Typing `tmp_path: Path` explicitly defines it as a Path object, improving code r # compute and test histogram Here: + - **tmp_path_factory: pytest.TempPathFactory:** Indicates that `tmp_path_factory` is an instance of pytest’s `TempPathFactory`, responsible for creating temporary directories and paths during testing. - **fn: Path:** Identifies that `fn` is a `Path` object, emphasizing its role as a file path and clarifying the expected file operations. - **Return type -> Path:** Specifies the fixture returns a `Path` object, clarifying its expected structure. From aeda81b0b5489524c6915a21cd436072ed4f4280 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 1 Mar 2025 12:36:33 -0300 Subject: [PATCH 6/9] Apply suggestions from code review --- doc/en/how-to/types.rst | 194 ++++++---------------------------------- 1 file changed, 25 insertions(+), 169 deletions(-) diff --git a/doc/en/how-to/types.rst b/doc/en/how-to/types.rst index e2ab22b25c4..7150e0e8e88 100644 --- a/doc/en/how-to/types.rst +++ b/doc/en/how-to/types.rst @@ -6,50 +6,41 @@ Enhancing Type Annotations with Pytest This page assumes the reader is familiar with Python's typing system and its advantages. For more information, refer to `Python's Typing Documentation `_. -Why Type Tests? +Why type tests? --------------- -Typing tests in pytest provide unique advantages distinct from typing production code. Typed tests emphasize robustness in edge cases and diverse datasets. -Type annotations provide an additional layer of validation, reducing the risk of runtime failures. +Typing tests provides significant advantages: -- **Test Clarity:** Clearly defines expected inputs and outputs, improving readability, especially in complex or parameterized tests. +- **Readability:** Clearly defines expected inputs and outputs, improving readability, especially in complex or parameterized tests. -- **Type Safety:** Helps catch mistakes in test data early, reducing runtime errors. +- **Refactoring:** This is the main benefit in typing tests, as it will greatly help with refactoring, letting the type checker point out the necessary changes in both production and tests, without needing to run the full test suite. -- **Refactoring Support:** Serves as in-code documentation, clarifying data expectations and minimizing errors during test suite modifications. - -These benefits make typed tests a powerful tool for maintaining clarity, consistency, and safety throughout the testing process. - -Typing Test Functions ---------------------- -By adding type annotations to test functions, tests are easier to read and understand. -This is particularly helpful when developers need to refactor code or revisit tests after some time. - -For example: +For production code, typing also helps catching some bugs that might not be caught by tests at all (regardless of coverage), for example: .. code-block:: python - import pytest + def get_caption(target: int, items: list[tuple[int, str]]) -> str: + for value, caption in items: + if value == target: + return caption + + +The type checker will correctly error out that the function might return `None`, however even a full coverage test suite might miss that case: +.. code-block:: python - def add(a: int, b: int) -> int: - return a + b + def test_get_caption() -> None: + assert get_caption(10, [(1, "foo"), (10, "bar")]) == "bar" - def test_add() -> None: - result = add(2, 3) - assert result == 5 +Note the code above has 100% coverage, but the bug is not caught (of course the example is "obvious", but serves to illustrate the point). -Here, `test_add` is annotated with `-> None`, as it does not return a value. -While `-> None` typing may seem unnecessary, it ensures type checkers validate the function and helps identifying potential issues during refactoring. -Typing Fixtures +Typing fixtures --------------- -Fixtures in pytest helps set up data or provides resources needed for tests. -Adding type annotations to fixtures makes it clear what data they return, which helps with debugging and readability. -* Basic Fixture Typing +To type fixtures in pytest, just add normal types to the fixture functions -- there is nothing special that needs to be done just because of the `fixture` decorator. .. code-block:: python @@ -59,178 +50,43 @@ Adding type annotations to fixtures makes it clear what data they return, which @pytest.fixture def sample_fixture() -> int: return 38 + +In the same manner, the fixtures passed to test functions need be annotated with the fixture's return type: +.. code-block:: python def test_sample_fixture(sample_fixture: int) -> None: assert sample_fixture == 38 -Here, `sample_fixture()` is typed to return an `int`. This ensures consistency and helps identify mismatch types during refactoring. - - -* Typing Fixtures with Lists and Dictionaries -This example shows how to use List and Dict types in pytest. - -.. code-block:: python - - from typing import List, Dict - import pytest - - - @pytest.fixture - def sample_list() -> List[int]: - return [5, 10, 15] - +From the POV of the type checker, it does not matter that `sample_fixture` is actually a fixture managed by pytest, all it matters to it is that `sample_fixture` is a parameter of type `int`. - def test_sample_list(sample_list: List[int]) -> None: - assert sum(sample_list) == 30 - - @pytest.fixture - def sample_dict() -> Dict[str, int]: - return {"a": 50, "b": 100} - - - def test_sample_dict(sample_dict: Dict[str, int]) -> None: - assert sample_dict["a"] == 50 - -Annotating fixtures with types like List[int] and Dict[str, int] ensures data consistency and helps prevent runtime errors when performing operations. -This ensures that only `int` values are allowed in the list and that `str` keys map to `int` values in the dictionary, helping avoid type-related issues. - -Typing Parameterized Tests --------------------------- -With `@pytest.mark.parametrize`, adding typing annotations to the input parameters reinforce type safety and reduce errors with multiple data sets. - -For example, you are testing if adding 1 to `input_value` results in `expected_output` for each set of arguments. +The same logic applies to `@pytest.mark.parametrize`: .. code-block:: python import pytest - @pytest.mark.parametrize("input_value, expected_output", [(1, 2), (5, 6), (10, 11)]) def test_increment(input_value: int, expected_output: int) -> None: assert input_value + 1 == expected_output -Here, typing clarifies that both `input_value` and `expected_output` are expected as integers, promoting consistency. -While parameterized tests can involve varied data types and that annotations simplify maintenance when datasets grow. -Typing for Monkeypatching -------------------------- -Monkeypatching modifies functions or environment variables during runtime. -Adding typing, such as `monkeypatch: pytest.MonkeyPatch`, clarifies the expected patching behaviour and reduces the risk of errors. -* Example of Typing Monkeypatching Environment Variables - -This example is based on the pytest documentation for `Monkeypatching `_, with the addition of typing annotations. +The same logic applies when typing fixture functions which receive other fixtures: .. code-block:: python - # contents of our original code file e.g. code.py - import pytest - import os - from typing import Optional - - - def get_os_user_lower() -> str: - """Simple retrieval function. Returns lowercase USER or raises OSError.""" - username: Optional[str] = os.getenv("USER") - - if username is None: - raise OSError("USER environment is not set.") - - return username.lower() - - - # contents of our test file e.g. test_code.py @pytest.fixture def mock_env_user(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("USER", "TestingUser") - @pytest.fixture - def mock_env_missing(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.delenv("USER", raising=False) - - - def test_upper_to_lower(mock_env_user: None) -> None: - assert get_os_user_lower() == "testinguser" - - - def test_raise_exception(mock_env_missing: None) -> None: - with pytest.raises(OSError): - _ = get_os_user_lower() - -Here: - -- **username: Optional[str]:** Indicates the variable `username` may either be a string or `None`. -- **get_os_user_lower() -> str:** Specifies this function will return a string, providing explicit return value type. -- **monkeypatch fixture is typed as pytest.MonkeyPatch:** Shows that it will provide an object for patching environment variables during the test. This clarifies the intended use of the fixture and helps developers to use it correctly. -- **Fixture return -> None, like mock_env_user:** Specifies they do not return any value, but instead modify the test environment. - -Typing annotations can also be extended to `monkeypatch` usage in pytest for class methods, instance attributes, or standalone functions. -This enhances type safety and clarity when patching the test environment. - - -Typing Temporary Directories and Paths --------------------------------------- -Temporary directories and paths are commonly used in pytest to create isolated environments for testing file and directory operations. -The `tmp_path` and `tmpdir` fixtures provide these capabilities. -Adding typing annotations enhances clarity about the types of objects these fixtures return, which is particularly useful when performing file operations. - -Below examples are based on the pytest documentation for `Temporary Directories and Files in tests `_, with the addition of typing annotations. - -* Typing with `tmp_path` for File Creation - -.. code-block:: python - - import pytest - from pathlib import Path - # content of test_tmp_path.py - CONTENT = "content" - - - def test_create_file(tmp_path: Path) -> None: - d = tmp_path / "sub" - d.mkdir() - p = d / "hello.txt" - p.write_text(CONTENT, encoding="utf-8") - assert p.read_text(encoding="utf-8") == CONTENT - assert len(list(tmp_path.iterdir())) == 1 - -Typing `tmp_path: Path` explicitly defines it as a Path object, improving code readability and catching type issues early. - -* Typing with `tmp_path_factory` fixture for creating temporary files during a session - -.. code-block:: python - - # contents of conftest.py - import pytest - from pathlib import Path - - - @pytest.fixture(scope="session") - def image_file(tmp_path_factory: pytest.TempPathFactory) -> Path: - img = compute_expensive_image() - fn: Path = tmp_path_factory.mktemp("data") / "img.png" - img.save(fn) - return fn - - - # contents of test_image.py - def test_histogram(image_file: Path) -> None: - img = load_image(image_file) - # compute and test histogram - -Here: - -- **tmp_path_factory: pytest.TempPathFactory:** Indicates that `tmp_path_factory` is an instance of pytest’s `TempPathFactory`, responsible for creating temporary directories and paths during testing. -- **fn: Path:** Identifies that `fn` is a `Path` object, emphasizing its role as a file path and clarifying the expected file operations. -- **Return type -> Path:** Specifies the fixture returns a `Path` object, clarifying its expected structure. -- **image_file: Path:** Defines `image_file` as a Path object, ensuring compatibility with `load_image`. Conclusion ---------- + Incorporating typing into pytest tests enhances **clarity**, improves **debugging** and **maintenance**, and ensures **type safety**. These practices lead to a **robust**, **readable**, and **easily maintainable** test suite that is better equipped to handle future changes with minimal risk of errors. From b3b981e4f2f94c19d56293cac6855cd276672b8f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 15:36:53 +0000 Subject: [PATCH 7/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/en/how-to/types.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/en/how-to/types.rst b/doc/en/how-to/types.rst index 7150e0e8e88..ff66f66f740 100644 --- a/doc/en/how-to/types.rst +++ b/doc/en/how-to/types.rst @@ -9,7 +9,7 @@ For more information, refer to `Python's Typing Documentation int: return 38 - + In the same manner, the fixtures passed to test functions need be annotated with the fixture's return type: .. code-block:: python @@ -67,6 +67,7 @@ The same logic applies to `@pytest.mark.parametrize`: import pytest + @pytest.mark.parametrize("input_value, expected_output", [(1, 2), (5, 6), (10, 11)]) def test_increment(input_value: int, expected_output: int) -> None: assert input_value + 1 == expected_output From c616f52808b5b1477fedf4ecafb8bf08cf11a6f2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 1 Mar 2025 12:42:55 -0300 Subject: [PATCH 8/9] Move to explanation --- doc/en/explanation/index.rst | 1 + doc/en/{how-to => explanation}/types.rst | 22 +++++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) rename doc/en/{how-to => explanation}/types.rst (87%) diff --git a/doc/en/explanation/index.rst b/doc/en/explanation/index.rst index 2edf60a5d8b..2606d7d4b34 100644 --- a/doc/en/explanation/index.rst +++ b/doc/en/explanation/index.rst @@ -12,5 +12,6 @@ Explanation fixtures goodpractices pythonpath + types ci flaky diff --git a/doc/en/how-to/types.rst b/doc/en/explanation/types.rst similarity index 87% rename from doc/en/how-to/types.rst rename to doc/en/explanation/types.rst index ff66f66f740..827a2bf02b6 100644 --- a/doc/en/how-to/types.rst +++ b/doc/en/explanation/types.rst @@ -1,10 +1,12 @@ .. _types: -Enhancing Type Annotations with Pytest -====================================== +Typing in pytest +================ -This page assumes the reader is familiar with Python's typing system and its advantages. -For more information, refer to `Python's Typing Documentation `_. +.. note:: + This page assumes the reader is familiar with Python's typing system and its advantages. + + For more information, refer to `Python's Typing Documentation `_. Why type tests? --------------- @@ -37,8 +39,8 @@ Note the code above has 100% coverage, but the bug is not caught (of course the -Typing fixtures ---------------- +Using typing in test suites +--------------------------- To type fixtures in pytest, just add normal types to the fixture functions -- there is nothing special that needs to be done just because of the `fixture` decorator. @@ -61,20 +63,16 @@ In the same manner, the fixtures passed to test functions need be annotated with From the POV of the type checker, it does not matter that `sample_fixture` is actually a fixture managed by pytest, all it matters to it is that `sample_fixture` is a parameter of type `int`. -The same logic applies to `@pytest.mark.parametrize`: +The same logic applies to :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>`: .. code-block:: python - import pytest - @pytest.mark.parametrize("input_value, expected_output", [(1, 2), (5, 6), (10, 11)]) def test_increment(input_value: int, expected_output: int) -> None: assert input_value + 1 == expected_output - - The same logic applies when typing fixture functions which receive other fixtures: .. code-block:: python @@ -84,8 +82,6 @@ The same logic applies when typing fixture functions which receive other fixture monkeypatch.setenv("USER", "TestingUser") - - Conclusion ---------- From b3781db324bca02e7cc8aea83efb0ecdafac9879 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 1 Mar 2025 12:53:31 -0300 Subject: [PATCH 9/9] Update 12842.doc.rst --- changelog/12842.doc.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog/12842.doc.rst b/changelog/12842.doc.rst index 22d351fc3d8..0a0f5c5bc23 100644 --- a/changelog/12842.doc.rst +++ b/changelog/12842.doc.rst @@ -1,2 +1,3 @@ -Added dedicated page about using types with pytest +Added dedicated page about using types with pytest. + See :ref:`types` for detailed usage.