diff --git a/examples/pure-hatch/pyproject.toml b/examples/pure-hatch/pyproject.toml index c6ef1931..8efda3a7 100644 --- a/examples/pure-hatch/pyproject.toml +++ b/examples/pure-hatch/pyproject.toml @@ -1,3 +1,4 @@ +# pyproject.toml example build setup to use hatchling and hatch_vcs [build-system] requires = ["hatchling"] build-backend = "hatchling.build" @@ -15,28 +16,49 @@ keywords = ["pyOpenSci", "python packaging"] readme = "README.md" license = "BSD-3-Clause" classifiers = [ - "Programming Language :: Python :: 3", - "Operating System :: OS Independent", + # How mature is this project? Common values are + "Development Status :: 4 - Beta", + + # Indicate who your project is intended for + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] dependencies = [ - "dependency-package-name-1", - "dependency-package-name-2", + "pandas", + "xarray", + "geopandas", + "matplotlib" ] -[project.optional-dependencies] -tests = [ +[dependency-groups] +test = [ "pytest", "pytest-cov" ] + lint = [ "black", - "flake8" + "ruff" ] + docs = [ "sphinx", "pydata-sphinx-theme" ] +dev = [ + {include-group = "test"}, + {include-group = "lint"} +] + +[project.optional-dependencies] +plot = ["bokeh"] +test = ["pytest", "pytest-cov"] +docs = ["sphinx", "pydata-sphinx-theme"] + [tool.ruff] select = [ "E", # pycodestyle errors @@ -50,3 +72,18 @@ known-first-party = ["examplePy"] [tool.pytest.ini_options] pythonpath = "src" + +# Example hatch vcs setup in the pyproject.toml file +[tool.hatch.build.hooks.vcs] +version-file = "_version.py" + +[tool.hatch.envs.test] +development-group= [ + "tests", +] + +[tool.hatch.envs.test.scripts] +run = "pytest {args:--cov=test --cov-report=term-missing --cov-report=xml}" + +[[tool.hatch.envs.test.matrix]] +python = ["3.10", "3.11", "3.12"] diff --git a/examples/pure-hatch/src/examplePy/numbers.py b/examples/pure-hatch/src/examplePy/numbers.py new file mode 100644 index 00000000..94bccf8e --- /dev/null +++ b/examples/pure-hatch/src/examplePy/numbers.py @@ -0,0 +1,18 @@ +# src/examplePy/numbers.py +def add_numbers(a: float, b: float) -> float: + """ + Add two numbers together and return the result. + + Parameters + ---------- + a : float + The first number to add. + b : float + The second number to add. + + Returns + ------- + float + The sum of the two numbers. + """ + return a + b diff --git a/examples/pure-hatch/src/examplePy/temperature.py b/examples/pure-hatch/src/examplePy/temperature.py index e19f96a9..f553f890 100644 --- a/examples/pure-hatch/src/examplePy/temperature.py +++ b/examples/pure-hatch/src/examplePy/temperature.py @@ -1,4 +1,5 @@ -def celsius_to_fahrenheit(celsius): +# src/examplePy/temperature.py +def celsius_to_fahrenheit(celsius: float) -> float: """ Convert temperature from Celsius to Fahrenheit. @@ -12,7 +13,7 @@ def celsius_to_fahrenheit(celsius): return fahrenheit -def fahrenheit_to_celsius(fahrenheit): +def fahrenheit_to_celsius(fahrenheit: float) -> float: """ Convert temperature from Fahrenheit to Celsius. @@ -24,3 +25,40 @@ def fahrenheit_to_celsius(fahrenheit): """ celsius = (fahrenheit - 32) * 5 / 9 return celsius + + +def average_temperature(temps: list[float]) -> float: + """ + Calculate average temperature from a list. + + Parameters + ---------- + temps : list + List of temperatures. + + Returns + ------- + float + Average temperature. + """ + return sum(temps) / len(temps) + + +def convert_and_average(temps_celsius: list[float]) -> float: + """ + Convert list of Celsius temps to Fahrenheit and + calculate the average. + + Parameters + ---------- + temps_celsius : list + List of Celsius temperatures. + + Returns + ------- + float + Average temperature in Fahrenheit. + """ + temps_fahrenheit = [celsius_to_fahrenheit(t) + for t in temps_celsius] + return average_temperature(temps_fahrenheit) diff --git a/examples/pure-hatch/tests/examplePy/test_numbers.py b/examples/pure-hatch/tests/examplePy/test_numbers.py new file mode 100644 index 00000000..f0368f3f --- /dev/null +++ b/examples/pure-hatch/tests/examplePy/test_numbers.py @@ -0,0 +1,17 @@ +# tests/examplePy/test_numbers.py +from examplePy.numbers import add_numbers + + +def test_add_numbers(): + """Test the add_numbers function.""" + # test with positive numbers + result = add_numbers(2, 3) + assert result == 5, f"Expected 5, but got {result}" + + # test with negative numbers + result2 = add_numbers(-1, 4) + assert result2 == 3, f"Expected 3, but got {result2}" + + # test with zero + result3 = add_numbers(0, 5) + assert result3 == 5, f"Expected 5, but got {result3}" diff --git a/examples/pure-hatch/tests/examplePy/test_temperature.py b/examples/pure-hatch/tests/examplePy/test_temperature.py index 9c6e3d5a..c8ac1eef 100644 --- a/examples/pure-hatch/tests/examplePy/test_temperature.py +++ b/examples/pure-hatch/tests/examplePy/test_temperature.py @@ -5,6 +5,44 @@ from examplePy import temperature +def test_convert_and_average(): + """ + Test that convert_and_average correctly combines conversion + and averaging. + """ + # Test with known values: [0, 10, 20] Celsius + # Should average to 10 Celsius = 50 Fahrenheit + temps_celsius = [0, 10, 20] + result = temperature.convert_and_average(temps_celsius) + assert abs(result - 50.0) < 0.01 + + # Test with different values + temps_celsius = [0, 100] + result = temperature.convert_and_average(temps_celsius) + # Average of 32 and 212 Fahrenheit = 122 + assert abs(result - 122.0) < 0.01 + + +def test_temperature_workflow(): + """ + Test the complete temperature processing workflow. + + This end-to-end test provides sample temperature data in + Celsius, processes it through the full workflow + (conversion and averaging), and verifies the output is + correct. + """ + # Sample temperature data in Celsius + temps_celsius = [0, 10, 20] + + # Run the complete workflow + result = temperature.convert_and_average(temps_celsius) + + # Verify the output + # Average of 32, 50, and 68 Fahrenheit = 50 Fahrenheit + assert abs(result - 50.0) < 0.01 + + def test_fahrenheit_to_celsius_positive(): """Test F to C calculation for positive values""" value = 95 diff --git a/package-structure-code/declare-dependencies.md b/package-structure-code/declare-dependencies.md index 56519a44..ecdf68a9 100644 --- a/package-structure-code/declare-dependencies.md +++ b/package-structure-code/declare-dependencies.md @@ -32,32 +32,28 @@ While `pyproject.toml` is now the standard, you may sometimes encounter older ap Specifying dependencies in the [project.dependency] array of your `pyproject.toml` file ensures that libraries needed to run your package are correctly installed into a user's environment. For instance, if your package requires Pandas to run properly, and you add Pandas to the `project.dependency` array, Pandas will be installed into the users' environment when they install your package using uv, pip, or conda. -```toml -[project] -... -... -... -dependencies = [ - "pandas", -] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n..." +:start-at: dependencies = [ +:end-at: ] +::: Development dependencies make it easier for contributors to work on your package. You can set up instructions for running specific workflows, such as tests, linting, and even typing, that automatically install groups of development dependencies. These dependencies can be stored in arrays (lists of dependencies) within a `[development-group]` table. -```toml -[development-group] -tests = [ - "pytest", - "pytest-cov" -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [development-group] +:end-before: lint +::: ### Types of dependencies There are three different types of dependencies that you will learn about on this page: 1. **Required dependencies:** These are dependencies that need to be installed for your package to work correctly in a user's environment. You add these dependencies to the `[project.dependencies]` table in your pyproject.toml file. -2. **Feature Dependencies:** These are dependencies that are required if a user wants to access additional functionality (that is not core) to your package. Store these in the `[project.optional.dependencies]` table or your pyproject.toml file. +2. **Feature Dependencies:** These are dependencies that are required if a user wants to access additional functionality (that is not core) to your package. Store these in the `[project.optional-dependencies]` table or your pyproject.toml file. 3. **Development Dependencies:** These dependencies are required if someone wants to develop or work on your package. These include instance linters, testing tools like pytest and mypy are examples of development dependencies. Store these in the `[project.dependency.groups]` table or your pyproject.toml file. :::{tip} @@ -76,17 +72,13 @@ You can add your required dependencies to the `dependencies` array in the your package with uv, pip, or conda, these dependencies will be automatically installed alongside your package in their environment. -```toml -[project] -name = "examplePy" -authors = [ - {name = "Some Maintainer", email = "some-email@pyopensci.org"}, -] -dependencies = [ - "pandas", - "matplotlib", -] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n..." +:start-at: dependencies = [ +:end-at: ] +::: :::{tip} Try your best to minimize dependencies whenever possible. Remember that @@ -143,18 +135,17 @@ Optional (also referred to as feature) dependencies can be installed by users as Place these dependencies in the `[project.optional-dependencies]` table. -```toml -[project] -... -... -... -[optional.dependencies] -plot = ["bokeh"] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n..." +:start-at: [project.optional-dependencies] +:end-at: plot = ["bokeh"] +::: When a user installs your package, uv, pip, or conda automatically installs all required dependencies. Optional dependencies are only installed if the user explicitly requests them. -:::{dropdown} How to Add optional.dependencies using UV +:::{dropdown} How to Add project.optional-dependencies using UV :icon: eye :color: primary @@ -169,7 +160,7 @@ uv add --optional feature pandas Will add this to your pyproject.toml file: ```toml -[optional.dependencies] +[project.optional-dependencies] feature = [ "pandas>=2.3.3", ] @@ -208,12 +199,12 @@ within a `[development-groups]` table. Similar to optional-dependencies, you can create separate subgroups or arrays with names using the syntax: `group-name = ["dep1", "dep2"]` -```toml -[development-groups] -tests = ["pytest", "pytest-cov"] -docs = ["sphinx", "pydata-sphinx-theme"] -lint = ["ruff", "black"] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [development-group] +:end-before: [project.optional-dependencies] +::: + :::{dropdown} How to Add [development.group] using UV :icon: eye @@ -361,12 +352,12 @@ installation conflicts. You can also create combined groups that reference other groups: -```toml -[project.optional-dependencies] -test = ["pytest", "pytest-cov"] -docs = ["sphinx", "pydata-sphinx-theme"] -dev = ["your-package[test,docs]", "build", "twine"] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [project.optional-dependencies] +:end-before: [tool.ruff] +::: Then install everything with pip install or uv sync as needed: diff --git a/package-structure-code/publish-python-package-pypi-conda.md b/package-structure-code/publish-python-package-pypi-conda.md index 881d321e..4b064e10 100644 --- a/package-structure-code/publish-python-package-pypi-conda.md +++ b/package-structure-code/publish-python-package-pypi-conda.md @@ -28,7 +28,7 @@ Once you have published both package distributions (the source distribution and (publish-pypi-conda)= ## What is PyPI -[PyPI](https://pypi.org/) is an online Python package repository that +[PyPI](https://pypi.org/) (p/aɪ/.pi/aɪ/ or p/aɪ/p/aɪ/) is an online Python package repository that you can use to both find and install and publish your Python package. There is also a test PyPI repository where you can test publishing your package prior to the final publication on PyPI. diff --git a/package-structure-code/pyproject-toml-python-package-metadata.md b/package-structure-code/pyproject-toml-python-package-metadata.md index a35a0faa..b2ec3f66 100644 --- a/package-structure-code/pyproject-toml-python-package-metadata.md +++ b/package-structure-code/pyproject-toml-python-package-metadata.md @@ -7,7 +7,7 @@ 2. There are two _required_ fields in the **[project]** table: **name=** and **version=**. 3. Add metadata to the classifiers section of your `pyproject.toml` file to make it easier for users to find your project on PyPI. 4. When you are adding classifiers to the [project] table, only use valid values from [PyPI’s classifier page](https://PyPI.org/classifiers/). An invalid value here will raise an error when you build your package or publish to PyPI. -5. There is no specific order for tables in the `pyproject.toml` file. However fields need to be placed within the correct table sections. For example `requires =` always need to be associated with the **[build-system]** table. +5. There is no specific order for tables in the `pyproject.toml` file. However, fields need to be placed within the correct table sections. For example `requires =` always needs to be associated with the **[build-system]** table. ::: @@ -161,29 +161,65 @@ what dependencies your package requires. ## Add dependencies to your pyproject.toml file -The `pyproject.toml` file is a modern replacement for the `requirements.txt` file, which has been traditionally used to store development dependencies and also configuration for tools such as pytest, black, and others. +### Required dependencies +A `requirements.txt` file has been traditionally used to specify dependencies, but modern practice puts these +in the `pyproject.toml` file. Required dependencies are specified under the `[project]` section as a list of strings: -To add development dependencies to your build, add a `[dependency-groups]` array to your pyproject.toml file. +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[project]\n...\n...\n...\n" +:start-at: "dependencies = [" +:end-at: ] +::: -Then specify dependency groups as follows: +### Optional dependencies +Optional dependencies are specified under the `[project.optional-dependencies]` section and are intended to give users +options for including additional dependencies with their installation. Optional dependencies are collected together +as a list of strings and assigned to a name: :::{literalinclude} ../examples/pure-hatch/pyproject.toml :language: toml -:start-at: [project.optional-dependencies] -:end-before: [tool.ruff] +:start-at: "[project.optional-dependencies]" +:end-at: docs ::: -Following the above example, you install dependencies like this: +Named dependency lists can be invoked by users on installation to include those specific dependencies on installation. +Here is an example installing the test and docs dependencies using pip: + +- `python -m pip install examplePy[test,docs]` + +Other installation tools tend to follow a similar pattern where optional dependencies are concatenated as +a list to the package name. -- `python -m pip install -e .[tests]` +### Dependency Groups +Dependency groups are a way to group package requirements in the `pyproject.toml` file but exclude them in the +project metadata when it is built. Because it is not included in the project metadadata, users will not be able to +invoke dependency groups when installing from a package index such as PyPI, but they can be accessed by developers +who have all the project data (e.g. through cloning a repository). Optional dependencies are intended for package +consumers and dependency groups are intended for package maintainers and contributors. -- pip install --group test _# requires pip 25.1 or greater_ +To add development dependencies to your build, add a `[dependency-groups]` array to your pyproject.toml file. +Then specify dependency groups as follows: -The above will install both your package in editable mode and all of the dependencies declared in the tests section of your `[project.optional-dependencies]` table. +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [dependency-groups] +:end-before: "dev = [" +::: + +One of the capabilities that dependency groups have is the ability to make composition groups. +For example, `dev` dependency group could be composed of a `test` dependency group and a `lint` dependency group: + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[dependency-groups]\n...\n...\n...\n" +:start-at: "dev = [" +:end-at: ] +::: -To install all dependencies and also your package, you'd use: +To access groups using pip (requires pip 25.1 or higher), you can invoke them during installation like this: -`python -m pip install -e .[tests,lint,docs]` +- `python -m pip install --group dev` :::{admonition} Recursive dependencies :class: tip diff --git a/package-structure-code/python-package-distribution-files-sdist-wheel.md b/package-structure-code/python-package-distribution-files-sdist-wheel.md index 323c6a08..c015d1c1 100644 --- a/package-structure-code/python-package-distribution-files-sdist-wheel.md +++ b/package-structure-code/python-package-distribution-files-sdist-wheel.md @@ -36,38 +36,30 @@ The metadata that both build tools and PyPI uses to describe and understand your - The `[build-system]` table in your pyproject.toml file tells pip what [build backend tool](build_backends) you wish to use for creating your sdist and wheel distributions. -```toml -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [build-system] +:end-before: [project] +::: - And the dependencies section of your project table tells the build tool and PyPI what dependencies your project requires. -``` -dependencies = [ - "numpy", - "geopandas", -] -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: dependencies = [ +:end-before: [development-group] +::: 2. When the build tool creates your package distribution file (the file that you publish on PyPI), it also creates a METADATA file which PyPI can read and use to help users find your package. For example: - The `classifiers = ` section of your `[project]` table in the pyproject.toml file provides information that users on PyPI can use to filter for packages that address different topics or that support specific versions of python. -```toml -classifiers = [ - # How mature is this project? Common values are - "Development Status :: 4 - Beta", - - # Indicate who your project is intended for - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", -] -``` + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: classifiers = [ +:end-before: dependencies = [ +::: :::{admonition} What happened to setup.py and setup.cfg for metadata? :class: note diff --git a/package-structure-code/python-package-versions.md b/package-structure-code/python-package-versions.md index c835046c..574e0269 100644 --- a/package-structure-code/python-package-versions.md +++ b/package-structure-code/python-package-versions.md @@ -188,21 +188,20 @@ day workflow. #### Hatch example setup in your pyproject.toml -```toml -# pyproject.toml example build setup to use hatchling and hatch_vcs -[build-system] -requires = ["hatchling", "hatch-vcs"] -build-backend = "hatchling.build" -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: "# pyproject.toml example build setup to use hatchling and hatch_vcs" +:end-before: [project] +::: **Hatch_vcs** supports a fully automated package release and build, and push to PyPI workflow on GitHub. -```toml -# Example hatch vcs setup in the pyproject.toml file -[tool.hatch.build.hooks.vcs] -version-file = "_version.py" -``` +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: "# Example hatch vcs setup in the pyproject.toml file" +:end-at: version-file = "_version.py" +::: :::{tip} If you use **setuptools_scm**, then you might find **hatch_vcs** and **hatchling** to be the modern equivalent to your current setuptools / build workflow. diff --git a/tests/run-tests.md b/tests/run-tests.md index b7d8b766..eb8b31f5 100644 --- a/tests/run-tests.md +++ b/tests/run-tests.md @@ -181,29 +181,27 @@ using Hatch for packaging workflows. ### Setting up Hatch environments -Hatch environments are defined in your `pyproject.toml`. Rather than -duplicating dependencies, use `dependency-groups` to reference your test -dependencies: +Hatch environments can be defined in `pyproject.toml`. These environments can be used to specify what Hatch needs to run the +tests for the package. Test dependencies can be listed individually under `[tool.hatch.envs.test]`, +but if packages have already been grouped together under a name, those groups can be listed here instead. +Using named groups keeps the dependencies in one place and avoids duplication. -```toml -[dependency-groups] -tests = [ - "pytest>=7.0", - "pytest-cov", -] +The additional tools or options to run with the tests is specified under `[tool.hatch.envs.test.scripts]`. -[tool.hatch.envs.test] -dependency-groups = [ - "tests", -] +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [development-group] +:end-before: lint +::: -[tool.hatch.envs.test.scripts] -run = "pytest {args:--cov=test --cov-report=term-missing --cov-report=xml}" +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:start-at: [tool.hatch.envs.test] +:end-at: run +::: -``` -This approach keeps your test dependencies in one place and avoids -duplication. For a complete example, see our +For a complete example, see our [packaging template tutorial](https://www.pyopensci.org/tutorials/create-python-package.html) which shows a full `pyproject.toml` configuration. @@ -228,23 +226,14 @@ hatch run test:run ### Testing across Python versions To test across multiple Python versions, define a matrix in your -`pyproject.toml`: - -```toml -[dependency-groups] -tests = [ - "pytest>=7.0", - "pytest-cov", -] - -[tool.hatch.envs.test] -dependency-groups = [ - "tests", -] - -[[tool.hatch.envs.test.matrix]] -python = ["3.10", "3.11", "3.12"] -``` +`pyproject.toml` under `[[tool.hatch.envs.test.matrix]]`: + +:::{literalinclude} ../examples/pure-hatch/pyproject.toml +:language: toml +:prepend: "[tool.hatch.envs.test]\n...\n...\n" +:start-at: [[tool.hatch.envs.test.matrix]] +:end-at: python +::: Then run all versions with a single command: diff --git a/tests/test-types.md b/tests/test-types.md index c4bd7a55..349ddd01 100644 --- a/tests/test-types.md +++ b/tests/test-types.md @@ -30,48 +30,17 @@ numbers together. A unit test for that function ensures that when provided with two numbers, it returns the correct sum. This is a unit test because it checks a single unit (function) in isolation. -```python -# src/mypackage/math_utils.py -def add_numbers(a, b): - """ - Add two numbers together. - - Parameters - ---------- - a : float - First number. - b : float - Second number. - - Returns - ------- - float - Sum of a and b. - """ - return a + b -``` +:::{literalinclude} ../examples/pure-hatch/src/examplePy/numbers.py +:language: python +::: Example unit test for the above function. You'd run this test using the `pytest` command in your **tests/** directory. -```python -# tests/test_math_utils.py -from mypackage.math_utils import add_numbers - - -def test_add_numbers(): - """ - Test the add_numbers function. - """ - # Test with positive numbers - assert add_numbers(2, 3) == 5 - - # Test with negative numbers - assert add_numbers(-1, 4) == 3 - # Test with zero - assert add_numbers(0, 5) == 5 -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_numbers.py +:language: python +::: Notice that the tests above don't just test one case where numbers are added together. Instead, they test multiple scenarios: adding positive @@ -102,104 +71,19 @@ calculate statistics. An integration test would ensure that these functions work together correctly in a workflow where you convert temperatures and then analyze them. -```python -# src/mypackage/temperature_utils.py -def celsius_to_fahrenheit(celsius): - """ - Convert temperature from Celsius to Fahrenheit. - - Parameters - ---------- - celsius : float - Temperature in Celsius. - - Returns - ------- - float - Temperature in Fahrenheit. - """ - return (celsius * 9 / 5) + 32 - - -def fahrenheit_to_celsius(fahrenheit): - """ - Convert temperature from Fahrenheit to Celsius. - - Parameters - ---------- - fahrenheit : float - Temperature in Fahrenheit. - - Returns - ------- - float - Temperature in Celsius. - """ - return (fahrenheit - 32) * 5 / 9 - - -def average_temperature(temps): - """ - Calculate average temperature from a list. - - Parameters - ---------- - temps : list - List of temperatures. - - Returns - ------- - float - Average temperature. - """ - return sum(temps) / len(temps) - - -def convert_and_average(temps_celsius): - """ - Convert list of Celsius temps to Fahrenheit and - calculate the average. - - Parameters - ---------- - temps_celsius : list - List of Celsius temperatures. - - Returns - ------- - float - Average temperature in Fahrenheit. - """ - temps_fahrenheit = [celsius_to_fahrenheit(t) - for t in temps_celsius] - return average_temperature(temps_fahrenheit) -``` +:::{literalinclude} ../examples/pure-hatch/src/examplePy/temperature.py +:language: python +::: Here's an integration test that checks how the conversion and statistics functions work together: -```python -# tests/test_temperature_integration.py -from mypackage.temperature_utils import convert_and_average - - -def test_convert_and_average(): - """ - Test that convert_and_average correctly combines conversion - and averaging. - """ - # Test with known values: [0, 10, 20] Celsius - # Should average to 10 Celsius = 50 Fahrenheit - temps_celsius = [0, 10, 20] - result = convert_and_average(temps_celsius) - assert abs(result - 50.0) < 0.01 - - # Test with different values - temps_celsius = [0, 100] - result = convert_and_average(temps_celsius) - # Average of 32 and 212 Fahrenheit = 122 - assert abs(result - 122.0) < 0.01 -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_temperature.py +:language: python +:prepend: "# tests/examplePy/test_temperature.py" +:start-at: "from examplePy" +:end-at: "assert abs(result - 122.0) < 0.01" +::: This integration test verifies that the conversion and averaging functions work together as expected in a real workflow. @@ -246,30 +130,12 @@ temperature data and returns a summary average value. An end-to-end test would provide sample data, run the entire workflow, and verify that the final output is correct. -```python -# tests/test_temperature_e2e.py -from mypackage.temperature_utils import convert_and_average - - -def test_temperature_workflow(): - """ - Test the complete temperature processing workflow. - - This end-to-end test provides sample temperature data in - Celsius, processes it through the full workflow - (conversion and averaging), and verifies the output is - correct. - """ - # Sample temperature data in Celsius - temps_celsius = [0, 10, 20] - - # Run the complete workflow - result = convert_and_average(temps_celsius) - - # Verify the output - # Average of 32, 50, and 68 Fahrenheit = 50 Fahrenheit - assert abs(result - 50.0) < 0.01 -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_temperature.py +:language: python +:prepend: "# tests/examplePy/test_temperature.py" +:start-at: "def test_temperature_workflow" +:end-at: assert +::: This end-to-end test exercises the entire user workflow: providing sample data, converting and averaging it, and verifying the output diff --git a/tests/write-tests.md b/tests/write-tests.md index adae75f7..766bef22 100644 --- a/tests/write-tests.md +++ b/tests/write-tests.md @@ -61,43 +61,18 @@ For a good introduction to testing, see [this Software Carpentry lesson](https:/ Let's say you have a Python function that adds two numbers together. -```python -def add_numbers(a: float, b: float) -> float: - """ - Add two numbers together and return the result. - - Parameters - ---------- - a : float - The first number to add. - b : float - The second number to add. - - Returns - ------- - float - The sum of the two numbers. - """ - return a + b -``` +:::{literalinclude} ../examples/pure-hatch/src/examplePy/numbers.py +:language: python +:start-at: def add_numbers +::: A test to ensure that function runs as you might expect when provided with different numbers might look like this: -```python -def test_add_numbers(): - result = add_numbers(2, 3) - assert result == 5, f"Expected 5, but got {result}" - - result2 = add_numbers(-1, 4) - assert result2 == 3, f"Expected 3, but got {result2}" - - result3 = add_numbers(0, 0) - assert result3 == 0, f"Expected 0, but got {result3}" - -test_add_numbers() - -``` +:::{literalinclude} ../examples/pure-hatch/tests/examplePy/test_numbers.py +:language: python +:start-at: def test_add_numbers +::: ```` ### 🧩🐍 How do you know what type of tests to write?