From 532d87aae6eed27756d393bc5ed9e7fbfa7baa90 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Sep 2023 15:43:29 -0600 Subject: [PATCH] More test content --- ci-tests-data/index.md | 2 + ci-tests-data/run-tests.md | 115 +++++++++++++++ ci-tests-data/test-types.md | 69 +++++++++ ci-tests-data/tests.md | 222 +++++------------------------ images/code-cov-stravalib.png | Bin 0 -> 568356 bytes images/python-tests-puzzle-fit.png | Bin 0 -> 1927978 bytes images/python-tests-puzzle.png | Bin 0 -> 1137415 bytes 7 files changed, 222 insertions(+), 186 deletions(-) create mode 100644 ci-tests-data/run-tests.md create mode 100644 ci-tests-data/test-types.md create mode 100644 images/code-cov-stravalib.png create mode 100644 images/python-tests-puzzle-fit.png create mode 100644 images/python-tests-puzzle.png diff --git a/ci-tests-data/index.md b/ci-tests-data/index.md index 1926c8c2..3ca0c5d6 100644 --- a/ci-tests-data/index.md +++ b/ci-tests-data/index.md @@ -11,6 +11,8 @@ But why Intro Writing tests +Test types: unit, integration, functional +Run tests in CI & locally Package data CI diff --git a/ci-tests-data/run-tests.md b/ci-tests-data/run-tests.md new file mode 100644 index 00000000..4b2b0f78 --- /dev/null +++ b/ci-tests-data/run-tests.md @@ -0,0 +1,115 @@ +# Running your tests + +### What test suite tools should I use to run tests? + +We recommend using Pytest to set up testing infrastructure for your package as it is the most common testing tool used in the Python ecosystem. + +[Pytest package](https://docs.pytest.org/en/latest/) also has a number of extensions that can be used to add functionality such as: + +- [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) allows you to analyze the code coverage of your package during your tests, and generates a report that you can [upload to codecov](https://codecov.io/). + +```{note} +Reference the issue with running tests in vscode, breakpoints and –no-cov flag. Then link to tutorial that explains how to deal with this. +``` + +### Running tests + +**Tox** + +- [Tox](https://tox.wiki/en/latest/index.html#useful-links) is an automation tool that supports common steps such as building documentation, running tests across various versions of Python, and more. You can find [a nice overview of tox in the plasmaPy documentation](https://docs.plasmapy.org/en/stable/contributing/testing_guide.html#using-tox). + +**Make** + +- Some developers opt to use Make for writing tests due to its versatility; it's not tied to a specific language and can be employed to orchestrate various build processes. However, Make's unique syntax and approach can make it more challenging to learn, particularly if you're not already familiar with it. Make also won't manage environments for you like nox will do. + +**Hatch** + +- [Hatch](https://github.com/ofek/hatch) is a modern end-to-end packaging tool that also has a nice build backend called hatchling. Hatch offers a tox-like setup where you can run tests locally using different Python versions. If you are using hatch to support your packaging workflow, you may want to also use its testing capabilities rather than using nox. + +**[Nox](https://nox.thea.codes/):** is a Python-based automation tool that builds upon the features of both Make and Tox. Nox is designed to simplify and streamline testing and development workflows. Everything that you do with nox can be implemented using a Python-based interface. + +## Run your test suite + +Your package will be used by a diverse set of users who will be running various Python versions and using various operating systems. Thus you will want to run your test suite in all of these environments to identify issues that users may have before they run into them “in the wild”. + +There are two primary ways that you can run tests - locally on your computer and in your CI build. We discuss both below. + +## Section 2: Run tests on your computer + +In this guide we recommend Nox for running your tests locally. However you will also see packages using Tox or Hatch to create local environments with different versions of Python for you. Some packages use the more traditional Make approach. Note that Make does not manage environments for you. + +As discussed above, it’s ideal for you to make sure that you run your tests on the various combinations of operating systems and Python versions that your users may be using. + +### Why we like nox + +Nox simplifies the process of creating and managing different testing environments, allowing you to check how your code behaves across various Python versions and configurations. With Nox, you can define specific test scenarios, set up virtual environments, and run tests across operating systems with a single command. Running tests locally using tools like Nox help you create controlled environment(s) to run your tests, making sure your code works correctly on your own computer before sharing it with others. + +We recommend Nox for this purpose because you can also use it to setup other types of development builds including building your documentation, package distributions and more. Nox also supports working with conda, venv and other environment managers. + +### Working with Nox + +## Environments + +By default, Nox uses the Python built in ` venv` environment manager. A virtual environment (`venv`) is a self-contained Python environment that allows you to isolate and manage dependencies for different Python projects. It helps ensure that project-specific libraries and packages do not interfere with each other, promoting a clean and organized development environment. + +Nox uses `venv`` by default. An example of using nox to run tests in venv environments for python versions 3.9, 3.10 and 3.11 is below. + +```{warning} +Note that for the code below to work, you need to have all 3 versions of python installed on your computer for venv to find. +``` + +Below is an example of setting up nox to run tests using `venv`. + +```python +import nox + +# for this to run you will need to have python3.9, python3.10 and python3.11 installed on your computer. Otherwise nox will skip running tests for whatever versions are missing + +@nox.session(python=["3.9", "3.10", "3.11"]) +def test(session): + +# install +session.install(".[all]") +# install dev requirements +session.install("-r", "requirements-dev.txt") +# Run tests +session.run("pytest") + +``` + +Below is an example for setting up nox to use mamba (or conda). +Note that when you are using conda, it can automagically install +the various versions of Python that you need. You won't need to install all three Python versions if you use conda/mamba, like you do with `venv`. + +```python +import nox + +# The syntax below allows you to use mamba / conda as your environment manager, if you use this approach you don’t have to worry about installing different versions of Python + +@nox.session(venv_backend='mamba', python=["3.9", "3.10", "3.11"]) +def test(session): + """Nox function that installs dev requirements and runs + tests on Python 3.9 through 3.11 + """ + + # Install dev requirements + session.install(".[all]") + # Install dev requirements + session.install("-r", "requirements-dev.txt") + # Run tests using any parameters that you need + session.run("pytest") +``` + +### Running tests on CI + +Running your test suite locally is useful as you develop code and also test new features or changes to the code base. However, you also will want to setup Continuous Integration (CI) to run your tests online. CI allows you to run all of your tests in the cloud. While you may only be able to run tests locally on a specific operating system that you run, in CI you can specify tests to run both on various versions of Python and across different operating systems. + +CI can also be triggered for pull requests and pushes to your repository. This means that every pull request that you, your maintainer team or a contributor submit, can be tested. In the end CI testing ensures your code continues to run as expected even as changes are made to the code base. [Read more about CI here. ](https://docs.google.com/document/d/1jmo2l5u02c_F1zZi0bAIYXeJ6HiIryJbXzsNbMQQX6o/edit#heading=h.3mx2na93o7bf) + +### CI Environment + +CI Environment: When you’re ready to publish your code online, you can setup Continuous Integration (CI). A CI platform will allow you to not only run your tests on various Python versions but also different operating systems like Mac, Linux and Windows. Tools like GitHub Actions and GitLab CI/CD make it easy for you to run tests on various Python versions, and even on Windows, Mac, and Linux. CI can finally be configured to ensure that tests run on every push and pull request to your repository. This ensures that any changes made to your package are tested across operations systems and Python versions before they are merged into the main branch of your codebase. <> + +By embracing these testing practices, you can ensure that your code runs as you expect it to across the diverse landscapes of user environments. + +pr checks. diff --git a/ci-tests-data/test-types.md b/ci-tests-data/test-types.md new file mode 100644 index 00000000..b9047b89 --- /dev/null +++ b/ci-tests-data/test-types.md @@ -0,0 +1,69 @@ +# Different types of tests: Unit, Integration & Functional Tests + +There are different types of tests that you want to consider when creating your +test suite: + +1. Unit tests +2. Integration +3. Functional (also known as end-to-end) tests + +Here you will learn about all three different types of tests. + +### Unit Tests + +A unit test in Python involves testing individual components or units of code in isolation to ensure that they work correctly. The goal of unit testing is to verify that each part of the software, typically at the function or method level, performs its intended task correctly. + +Unit tests can be compared to examining each piece of your puzzle to ensure parts of it are not broken. If all of the pieces of your puzzle don’t fit together, you will never complete it. Similarly, when working with code, tests ensure that each function, attribute, class, method works properly when isolated. + +**Unit test example:** Pretend that you have a function that converts a temperature value from Celsius to Fahrenheit. A test for that function might ensure that when provided with a value in Celsius, the function returns the correct value in degrees Fahrenheit. That function is a unit test. It checks a single unit (function) in your code. + +```{figure} ../images/python-tests-puzzle.png +:height: 350px +:alt: image of two puzzle pieces with some missing parts. The puzzle pieces are purple teal yellow and blue. The shapes of each piece don’t fit together. + +If puzzle pieces have missing ends, they can’t work together with other elements in the puzzle. The same is true with individual functions, methods and classes in your software. The code needs to work both individually and together to perform certain sets of tasks. + +``` + +### Integration tests + +Integration tests involve testing how parts of your package work together or integrate. Integration tests can be compared to connecting a bunch of puzzle pieces together to form a whole picture. Integration tests focus on how different pieces of your code fit and work together. + +For example, if you had a series of steps that collected temperature data in a spreadsheet, converted it from degrees celsius to Fahrenheit and then provided an average temperature for a particular time period. An integration test would ensure that all parts of that workflow behaved as expected. + +```{figure} ../images/python-tests-puzzle-fit.png +:height: 450px +:alt: image of puzzle pieces that all fit together nicely. The puzzle pieces are colorful - purple, green and teal. + +Your integration tests should ensure that parts of your code that are expected to work +together, do so as expected. + +``` + +### End-to-end (functional) tests + +End-to-end tests (also referred to as functional tests) in Python are like comprehensive checklists for your software. They simulate real user end-to-end workflows to make sure the code base supports real life applications and use-cases from start to finish. These tests help catch issues that might not show up in smaller tests and ensure your entire application or program behaves correctly. Think of them as a way to give your software a final check before it's put into action, making sure it's ready to deliver a smooth experience to its users. + +```{note} +For scientific packages, creating short tutorials that highlight core workflows that your package supports, that are run when your documentation is built could also serve as end-to-end tests. +``` + +### Comparing unit, integration and end-to-end tests + +Unit tests, integration tests, and end-to-end tests have complementary advantages and disadvantages. The fine-grained nature of unit tests make them well-suited for isolating where errors are occurring, but not very suitable for verifying that different sections of code work together. Integration and end-to-end tests verify that the different portions of the program work together, but are less well-suited for isolating where errors are occurring. A thorough test suite should have a mixture of unit tests, integration tests, and functional tests. + +# Code coverage + +Code coverage is the amount of your package's codebase that is run as a part of running your project's tests. A good rule of thumb is to ensure that \*\*every line of + +your code is run at least once during testing\**. However, note that good code coverage does not *guarantee\* that your package is well-tested. For example, you may run all of your lines of code, but not account for many edge-cases that users may have. Ultimately, you should think carefully about the way your package will be used, and decide whether your tests adequately cover all of that usage. + +A common service for analyzing code coverage is [codecov.io](https://codecov.io/). This project is free for open source tools, and will provide dashboards that tell you how much of your codebase is covered during your tests. We recommend setting up an account, and using codecov to keep track of your code coverage. + +```{figure} ../images/code-cov-stravalib.png +:height: 450px +:alt: Screenshot of the code cov service - showing test coverage for the stravalib package. in this image you can see a list of package modules and the associated number of lines and % lines covered by tests. at the top of the image you can see what branch is being evaluated and the path to the repository being shown. + +the Code cov platform is a useful tool if you wish to visually track code coverage. Using it you can not only get the same summary information that you can get with pytest-cov extension. You can also get a visual representation of what lines are covered by your tests and what lines are not covered. Code cove is mostly useful for evaluating unit tests and/or how much of your package code is "covered. It however will not evaluate things like integration tests and end-to-end workflows. b + +``` diff --git a/ci-tests-data/tests.md b/ci-tests-data/tests.md index f5d75cd3..c0cc7e55 100644 --- a/ci-tests-data/tests.md +++ b/ci-tests-data/tests.md @@ -1,42 +1,55 @@ # Writing tests for your code -Writing sets of test functions and methods, also known as test suites, for your package is important for both you as a maintainer, your users and any potential new contributors. Test suites consist of sets of functions, methods and classes that are written with the intention of making sure a specific part of your code works as you expected it to. +Writing sets of test functions and methods, also known as test suites, for your +package is important for both you as a maintainer, your users, and any potential +new contributors. Test suites consist of sets of functions, methods, and classes +that are written with the intention of making sure a specific part of your code +works as you expected it to. ## Why create tests for your package -Tests serve as a safety net for changes to your code. Tests help you identify and fix bugs before they reach users. They also provide confidence that code changes submitted by contributors won't break existing functionality. Finally, a comprehensive test suite can empower you to refactor code with less fear of unintentionally breaking things. Finally tests allows you to account for various edge cases that may break +Tests act as a safety net for code changes. They help you spot and rectify bugs +before they affect users. Tests also instill confidence that code alterations from +contributors won't breaking existing functionality. -your package's functionality. For instance what happens if a function expects a pandas ` dataframe` as an input but a user provides a numpy `array`? Does your code fail gracefully and let the user know what happened? Or does your code break leaving a user frustrated as they -don't know what broke things? +Also, a comprehensive +test suite empowers you to "fearlessly" refactor code as running tests after a bit refactor will likely identify any potential issues with the new code. + +Tests also enable you to address various edge cases that could impair +your package's functionality. For example, what occurs if a function expects a +pandas `dataframe` but a user supplies a numpy `array`? Does your code gracefully +handle this situation, providing clear feedback, or does it leave users +frustrated by an unexplained failure? :::{note} -For a good introduction to testing, see [this Software Carpentry lecture](https://swcarpentry.github.io/python-novice-inflammation/10-defensive.html) +For a good introduction to testing, see [this Software Carpentry lesson](https://swcarpentry.github.io/python-novice-inflammation/10-defensive.html) ::: +```{figure} ../images/python-tests-puzzle.png +:height: 350px + Imagine you're working on a puzzle where each puzzle piece represents a function, method, class or attribute in your Python package that you want other people to be able to use. Would you want to give someone a puzzle that has missing pieces or pieces that don't fit together? Providing people with the right puzzle pieces that work together can be compared to writing tests for your Python package. - +``` ### Why write tests? -Here's why writing tests for your Python package is important, especially if you're just starting out: - -- Catching Mistakes: Tests act like a safety net. When you make changes or add new features to your package, tests can quickly tell you if you accidentally broke something that was working fine before. -- Saving Time: Imagine you have a magic button that can automatically check if your package is still working properly. Tests are like that magic button! They can run all those checks for you in an instant, saving you lots of time. -- Teamwork Made Easy: If you're working with others, tests help everyone stay on the same page. Your tests explain how your package is supposed to work, making it easier for others to understand and contribute to your project. -- Fearless Refactoring: Refactoring means making improvements to your code without changing its behavior. Tests give you the courage to do this. You can confidently make changes because tests will quickly show you if something broke. -- Documentation: Tests serve as examples of how to use your package. When others want to use your tools, they can look at your tests to see how everything is supposed to fit together. -- Long-Term ease of maintenance: As your package grows, it becomes harder to keep track of everything. Tests ensure that your code behaves as expected, even as you make changes over time. This helps prevent future headaches! - -By running these tests, each time you or a contributor makes a change to your code-base, you can catch issues and things that may have changed in your code base. This ensures that your software behaves the way you expect it to. +Writing tests for your Python package is important because: -````{note} +- **Catch Mistakes:** Tests are a safety net. When you make changes or add new features to your package, tests can quickly tell you if you accidentally broke something that was working fine before. +- **Save Time:** Imagine you have a magic button that can automatically check if your package is still working properly. Tests are like that magic button! They can run all those checks for you saving you time. +- **Easier Collaboration:** If you're working with others, or have outside contributors, tests help everyone stay on the same page. Your tests explain how your package is supposed to work, making it easier for others to understand and contribute to your project. +- **Fearless Refactoring:** Refactoring means making improvements to your code structure without changing its behavior. Tests empower you to make these changes as if you break something, test failures will let you know. +- **Documentation:** Tests serve as technical examples of how to use your package. This can be helpful for a new technical contributor that wants to contribute code to your package. They can look at your tests to understand how parts of your code functionality fits together. +- **Long-Term ease of maintenance:** As your package evolves, tests ensure that your code continues to behave as expected, even as you make changes over time. Thus you are helping your future self when writing tests. +- **Easier pull request reviews** By running your tests in a CI framework such as GitHub actions, each time you or a contributor makes a change to your code-base, you can catch issues and things that may have changed in your code base. This ensures that your software behaves the way you expect it to. -### BREAKOUT - Test example +````{admonition} Test examples +:class: note -Let’s say you have a python function that adds two numbers a and b together. +Let’s say you have a Python function that adds two numbers a and b together. ```python def add_numbers(a, b): @@ -70,179 +83,16 @@ test_add_numbers() ### How do I know what type of tests to write? -

>>>>> gd2md-html alert: Definition ↓↓ outside of definition list. Missing preceding term(s)?
(Back to top)(Next alert)
>>>>>

- :::{note} - This section has been adapted from a presentation by Nick Murphy - https://zenodo.org/record/8185113 - -

>>>>> gd2md-html alert: Definition ↓↓ outside of definition list. Missing preceding term(s)?
(Back to top)(Next alert)
>>>>>

- ::: At this point, you may be wondering - what should you be testing in your package? Below are a few examples: -**test some typical cases: **Test that the package functions as you expect it to when users use it. For instance, if your package is supposed to add two numbers, test that the outcome value of adding those two numbers is correct. - -**Test special cases: **Sometimes there are special or outlier cases. For instance, if a function performs a specific calculation that may become problematic closer to the value = 0, test it with the input of both 0 and - -**Test at and near the expected boundaries : **If a function requires a value that is greater than or equal to 1, make sure that the function still works with both the values 1 and less than one and 1.001 as well (something close to the constraint value).. - -**Test that code fails correctly: **If a function requires a value greater than or equal to 1 - -test at 0.999 - Make sure that the function fails gracefully when given unexpected values and help and that the user can easily understand why if failed (provides a useful error message) - -## Different types of tests: Unit, Integration & Functional Tests - -There are different types of tests that you want to consider when creating your test suite: - -1. Unit tests -2. Integration -3. Functional (also known as end-to-end) tests - -### Unit Tests - -A unit test in Python is a type of software testing where individual components or units of code are tested in isolation to ensure that they work correctly. The goal of unit testing is to verify that each part of the software, typically at the function or method level, performs its intended task correctly. Unit tests can be compared to examining each piece of your puzzle to ensure parts of it are not broken. If all of the pieces of your puzzle don’t fit together, you will never complete it. Similarly, when working with code, tests ensure that each function, attribute, class, method works properly when isolated. - -**Unit test example: **Pretend that you have a function that converts a temperature value from Celsius to Fahrenheit. A test for that function might ensure that when provided with a value in Celsius, the function returns the correct value in degrees Fahrenheit. That function is a unit test. It checks a single unit (function) in your code. - -

>>>>> gd2md-html alert: inline image link here (to images/image1.png). Store image on your image server and adjust path/filename/extension if necessary.
(Back to top)(Next alert)
>>>>>

- -![alt_text](images/image1.png "image_tooltip") - -Caption If puzzle pieces have missing ends, they can’t work together with other elements in the puzzle. The same is true with individual functions, methods and classes in your software. The code needs to work both individually and together to perform certain sets of tasks. - -_Alt: image of two puzzle pieces with some missing parts. The puzzle pieces are purple teal yellow and blue. The shapes of each piece don’t fit together. _ - -### Integration tests - -Integration tests involve testing how parts of your package work together or integrate. Integration tests can be compared to connecting a bunch of puzzle pieces together to form a whole picture. Integration tests focus on how different pieces of your code fit and work together. For example, if you had a series of steps that collected temperature data in a spreadsheet, converted it from degrees celsius to Fahrenheit and then provided an average temperature for a particular time period. An integration test would ensure that all parts of that workflow behaved as expected. - -​​ - -

>>>>> gd2md-html alert: inline image link here (to images/image2.png). Store image on your image server and adjust path/filename/extension if necessary.
(Back to top)(Next alert)
>>>>>

- -![alt_text](images/image2.png "image_tooltip") - -### end to end (functional) tests - -End-to-end tests (also referred to as functional tests by some) in Python are like comprehensive checklists for your software. They simulate real user end-to-end workflows to make sure the code base supports real life applications and use-cases from start to finish. These tests help catch issues that might not show up in smaller tests and ensure your entire application or program behaves correctly. Think of them as a way to give your software a final check before it's put into action, making sure it's ready to deliver a smooth experience to its users. - -**Breakout: ** For scientific packages, creating short tutorials that highlight core workflows that your package supports, that are run when your documentation is built could also serve as end-to-end tests. - -### Comparing unit, integration and end-to-end tests - -Unit tests, integration tests, and end-to-end tests have complementary advantages and disadvantages. The fine-grained nature of unit tests make them well-suited for isolating where errors are occurring, but not very suitable for verifying that different sections of code work together. Integration and end-to-end tests verify that the different portions of the program work together, but are less well-suited for isolating where errors are occurring. A thorough test suite should have a mixture of unit tests, integration tests, and functional tests. - -## Code coverage - -Code coverage is the amount of your package's codebase that is run as a part of running your project's tests. A good rule of thumb is to ensure that \*\*every line of - -your code is run at least once during testing\**. However, note that good code coverage does not *guarantee\* that your package is well-tested. For example, you may run all of your lines of code, but not account for many edge-cases that users may have. Ultimately, you should think carefully about the way your package will be used, and decide whether your tests adequately cover all of that usage. - -A common service for analyzing code coverage is [codecov.io](https://codecov.io/). This project is free for open source tools, and will provide dashboards that tell you how much of your codebase is covered during your tests. We recommend setting up an account, and using codecov to keep track of your code coverage. - -

>>>>> gd2md-html alert: inline image link here (to images/image3.png). Store image on your image server and adjust path/filename/extension if necessary.
(Back to top)(Next alert)
>>>>>

- -![alt_text](images/image3.png "image_tooltip") - -### What test suite tools should I use to run tests? - -We recommend using Pytest to set up testing infrastructure for your package as it is the most common testing tool used in the Python ecosystem is - -[the pytest package](https://docs.pytest.org/en/latest/). Pytest also has - -a number of extensions that can be used in addition to pytest's core functionality. - -Such as [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) allows you to analyze the code coverage of your package during your tests, and generates a report that you can [upload to codecov](https://codecov.io/). - -_Breakout: probably useful to mention the issue with running tests in vscode, breakpoints and –no-cov flag. Then link to tutorial that explains how to deal with this._ - -### Running tests?? - -- [Nox:](https://nox.thea.codes/en/stable/index.html) Nox is a great Python focused tool that allows you to not only run tests in isolated environments, but also create workflows to run your documentation. - -**Breakout: **Other tools that you may see in the wild to run tests - -[Tox](https://tox.wiki/en/latest/index.html#useful-links): tox is an automation tool that supports common steps such as building documentation, running tests across various versions of python and more. You can find a nice overview of tox in the plasmaPy documentation. <TODO: ADD LINK TO GUIDE> - -**Make : **- Make is a more generic… (doesn’t handle envts) - -**Hatch:** - hatch is a modern end-to-end packaging tool that also has a nice backend called hatchling. Hatch offers a tox like setup where you can run tests locally using different python versions. If you are using hatch to support your packaging workflow, you may want to also use its testing capabilities rather than using nox! - -## Run your test suite - -Your package will be used by a diverse set of users who will be running various Python versions and using various operating systems. Thus you will want to run your test suite in all of these environments to identify issues that users may have before they run into them “in the wild”. - -There are two primary ways that you can run tests: - -### Locally on your computer - -In this guide we recommend Nox for running your tests locally. However you will also see packages using , Tox, or Hatch to create local environments with different versions of python for you. Some packages use the more traditional Make approach. Note that Make does not manage environments for you. - -### CI Environment - -CI Environment: When you’re ready to publish your code online, you can setup Continuous Integration (CI). A CI platform will allow you to not only run your tests on various python versions but also different operating systems like Mac, Linux and Windows. Tools like GitHub Actions and GitLab CI/CD make it easy for you to run tests on various Python versions, and even on Windows, Mac, and Linux. CI can finally be configured to ensure that tests run on every push and pull request to your repository. This ensures that any changes made to your package are tested across operations systems and python versions before they are merged into the main branch of your codebase. <> - -By embracing these testing practices, you can confidently ensure that your code runs as you expect it to across the diverse landscapes of user environments. - -pr checks. - -## Section 2: Running tests locally - -As discussed above, it’s ideal for you to make sure that you run your tests on the various combinations of operating systems and python versions that your users may be using. There are some great tools out there that you can use to do this including: - -- **Make: **Make is a build automation tool that you can use to create workflows such as running tests, building documentation and building your package. When using Make, you will create a Makefile that defines each workflow and the associated workflow “call” that you want to run . -- **Tox: **Tox is a command-line tool used in Python development for testing and packaging software projects. It automates the process of setting up virtual environments, running test suites across different Python versions, and ensuring the project is compatible and consistent across various environments. -- **Hatch:** Hatch has some built in functionality for running local tests across python versions that would replace the need for a tool like TOX. . If you are using Hatch, you may not need to use Nox. -- **Nox : **Nox is a Python-based automation tool that builds upon the features of both Make and Tox. Nox is designed to simplify and streamline testing and development workflows. Everything that you do with nox can be implemented using a Python-based interface. - -In this guide you will learn about Nox. Nox simplifies the process of creating and managing different testing environments, allowing you to check how your code behaves across various Python versions and configurations. With Nox, you can define specific test scenarios, set up virtual environments, and run tests across operating systems with a single command. Running tests locally using tools like Nox help you create controlled environment(s) to run your tests, making sure your code works correctly on your own computer before sharing it with others. - -We recommend Nox for this purpose because you can also use it to setup other types of development builds including building your documentation, package distributions and more. Nox also supports working with conda, venv and other environments. - -### Working with Nox - -More here ….. - -Nox uses venv by default - so to run using your venv envrts you can use - -Demo example below - -```python -import nox - -# for this to run you will need to have python3.9, python3.10 and python3.11 installed on your computer. Otherwise nox will skip running tests for whatever versions are missing - -@nox.session(python=["3.9", "3.10", "3.11"]) -def test(session): - -# install -session.install(".[all]") -# install dev requirements -session.install("-r", "requirements-dev.txt") -# Run tests -session.run("pytest") - -# the syntax below allows you to use mamba / conda as your environment manager, if you use this approach you don’t have to worry about installing different versions of python - -@nox.session(venv_backend='mamba', python=["3.9", "3.10", "3.11"]) -def test(session): - """Nox function that installs dev requirements and runs - tests on python 3.9 through 3.11 - """ - - # install dev requirements - session.install(".[all]") - #install dev requirements - session.install("-r", "requirements-dev.txt") - # Run tests using any parameters that you need - session.run("pytest") - -``` +- **Test some typical cases:** Test that the package functions as you expect it to when users use it. For instance, if your package is supposed to add two numbers, test that the outcome value of adding those two numbers is correct. -### Running tests on CI +- **Test special cases:** Sometimes there are special or outlier cases. For instance, if a function performs a specific calculation that may become problematic closer to the value = 0, test it with the input of both 0 and -Running your test suite locally is useful as you develop code and also test new features or changes to the code base. However, you also will want to setup Continuous Integration (CI) to run your tests online. CI allows you to run all of your tests in the cloud. While you may only be able to run tests locally on a specific operating system that you run, in CI you can specify tests to run both on various versions of Python and across different operating systems. +* **Test at and near the expected boundaries:** If a function requires a value that is greater than or equal to 1, make sure that the function still works with both the values 1 and less than one and 1.001 as well (something close to the constraint value).. -CI can also be triggered for pull requests and pushes to your repository. This means that every pull request that you, your maintainer team or a contributor submit, can be tested. In the end CI testing ensures your code continues to run as expected even as changes are made to the code base. [Read more about CI here. ](https://docs.google.com/document/d/1jmo2l5u02c_F1zZi0bAIYXeJ6HiIryJbXzsNbMQQX6o/edit#heading=h.3mx2na93o7bf) +* **Test that code fails correctly:** If a function requires a value greater than or equal to 1, then test at 0.999. Make sure that the function fails gracefully when given unexpected values and help and that the user can easily understand why if failed (provides a useful error message). diff --git a/images/code-cov-stravalib.png b/images/code-cov-stravalib.png new file mode 100644 index 0000000000000000000000000000000000000000..832ee3fced7b84921de1f3b64f4380ccd5dbf035 GIT binary patch literal 568356 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