Skip to content

Commit

Permalink
deploy: 6c16020
Browse files Browse the repository at this point in the history
  • Loading branch information
lwasser committed Dec 28, 2023
1 parent e61da13 commit c93824a
Show file tree
Hide file tree
Showing 51 changed files with 2,536 additions and 143 deletions.
Binary file modified .doctrees/environment.pickle
Binary file not shown.
Binary file modified .doctrees/index.doctree
Binary file not shown.
Binary file added .doctrees/tests/index.doctree
Binary file not shown.
Binary file added .doctrees/tests/test-types.doctree
Binary file not shown.
Binary file added .doctrees/tests/write-tests.doctree
Binary file not shown.
8 changes: 4 additions & 4 deletions CONTRIBUTING.html
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@


<li class="nav-item">
<a class="nav-link nav-internal" href="ci-and-testing/intro.html">
CI & Tests
<a class="nav-link nav-internal" href="tests/index.html">
Tests
</a>
</li>

Expand Down Expand Up @@ -339,8 +339,8 @@


<li class="nav-item">
<a class="nav-link nav-internal" href="ci-and-testing/intro.html">
CI & Tests
<a class="nav-link nav-internal" href="tests/index.html">
Tests
</a>
</li>

Expand Down
Binary file added _images/flower-puzzle-pyopensci.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/packaging-lifecycle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/pyopensci-puzzle-pieces-tests.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/python-tests-puzzle-fit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _images/python-tests-puzzle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions _sources/index.md.txt
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ Packaging <package-structure-code/intro>

```{toctree}
:hidden:
:caption: CI and Testing
:caption: Testing

Tests <tests/index>

CI & Tests <ci-and-testing/intro>
```
55 changes: 55 additions & 0 deletions _sources/tests/index.md.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Tests and data for your Python package

Tests are an important part of your Python package because they
provide a set of checks that ensure that your package is
functioning how you expect it to.

In this section you will learn more about the importance of writing
tests for your Python package and how you can setup infrastructure
to run your tests both locally and on GitHub.


::::{grid} 1 1 3 3
:class-container: text-center
:gutter: 3

:::{grid-item-card}
:link: write-tests
:link-type: doc

✨ Why write tests ✨
^^^

Learn more about the art of writing tests for your Python package.
Learn about why you should write tests and how they can help you and
potential contributors to your project.
:::

:::{grid-item-card}
:link: test-types
:link-type: doc

✨ Types of tests ✨
^^^
There are three general types of tests that you can write for your Python
package: unit tests, integration tests and end-to-end (or functional) tests. Learn about all three.
:::
::::


:::{figure-md} fig-target

<img src="../images/packaging-lifecycle.png" alt="" width="800px">

Graphic showing the elements of the packaging process.
:::

```{toctree}
:hidden:
:maxdepth: 2
:caption: Create & Run Tests

Intro <self>
Write tests <write-tests>
Test types <test-types>
```
156 changes: 156 additions & 0 deletions _sources/tests/test-types.md.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Test Types for Python packages

## Three 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. End-to-end (also known as Functional) tests

Each type of test has a different purpose. Here, you will learn about all three types of tests.

```{todo}
I think this page would be stronger if we did have some
examples from our package here: https://github.com/pyOpenSci/pyosPackage

```

## Unit Tests

A unit test 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.

```python
# Example package function
def celsius_to_fahrenheit(celsius):
"""
Convert temperature from Celsius to Fahrenheit.

Parameters:
celsius (float): Temperature in Celsius.

Returns:
float: Temperature in Fahrenheit.
"""
fahrenheit = (celsius * 9/5) + 32
return fahrenheit
```

Example unit test for the above function. You'd run this test using the `pytest` command in your **tests/** directory.

```python
import pytest
from temperature_converter import celsius_to_fahrenheit

def test_celsius_to_fahrenheit():
"""
Test the celsius_to_fahrenheit function.
"""
# Test with freezing point of water
assert pytest.approx(celsius_to_fahrenheit(0), abs=0.01) == 32.0

# Test with boiling point of water
assert pytest.approx(celsius_to_fahrenheit(100), abs=0.01) == 212.0

# Test with a negative temperature
assert pytest.approx(celsius_to_fahrenheit(-40), abs=0.01) == -40.0

```

```{figure} ../images/pyopensci-puzzle-pieces-tests.png
:height: 300px
:alt: image of puzzle pieces that all fit together nicely. The puzzle pieces are colorful - purple, green and teal.

Your unit tests should ensure each part of your code works as expected on its own.
```

## 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.

```python

def fahr_to_celsius(fahrenheit):
"""
Convert temperature from Fahrenheit to Celsius.

Parameters:
fahrenheit (float): Temperature in Fahrenheit.

Returns:
float: Temperature in Celsius.
"""
celsius = (fahrenheit - 32) * 5/9
return celsius

# Function to calculate the mean temperature for each year and the final mean
def calc_annual_mean(df):
# TODO: make this a bit more robust so we can write integration test examples??
# Calculate the mean temperature for each year
yearly_means = df.groupby('Year').mean()

# Calculate the final mean temperature across all years
final_mean = yearly_means.mean()

# Return a converted value
return fahr_to_celsius(yearly_means), fahr_to_celsius(final_mean)

```

```{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.

```

```{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.

```{figure} ../images/flower-puzzle-pyopensci.jpg
:height: 450px
:alt: Image of a completed puzzle showing a daisy

End-to-end or functional tests represent an entire workflow that you
expect your package to support.

```

End-to-end test also test how a program runs from start to finish. A tutorial that you add to your documentation that runs in CI in an isolated environment is another example of an end-to-end test.

```{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. However, unit tests are not useful 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. For example, when you refactor your code, it is possible that that your end-to-end tests will
break. But if the refactor didn't introduce new behavior to your existing
code, then you can rely on your unit tests to continue to pass, testing the
original functionality of your code.

It is important to note that you don't need to spend energy worrying about
the specifics surrounding the different types of tests. When you begin to
work on your test suite, consider what your package does and how you
may need to test parts of your package. Bring familiar with the different types of tests can provides a framework to
help you think about writing tests and how different types of tests can complement each other.
88 changes: 88 additions & 0 deletions _sources/tests/write-tests.md.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Write tests for your Python package

Writing code that tests your package code, also known as test suites, is important for you as a maintainer, your users, and package 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 write tests for your package?

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.

Writing tests for your Python package is important because:

- **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.

### Tests for user edge cases

Edge cases refer to unexpected or "outlier" ways that some users may use your package. Tests 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 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.

```

````{admonition} Test examples
:class: note

Let’s say you have a Python function that adds two numbers a and b together.

```python
def add_numbers(a, b):
return a + b
```

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()

```
````

🧩🐍

### How do I know what type of tests to write?

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

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, 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).
22 changes: 6 additions & 16 deletions ci-and-testing/intro.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
<link rel="canonical" href="https://www.pyopensci.org/package-review-guide/ci-and-testing/intro.html" />
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="prev" title="Python Package Code Style, Format and Linters" href="../package-structure-code/code-style-linting-format.html" />
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="docsearch:language" content="en"/>
</head>
Expand Down Expand Up @@ -177,9 +176,9 @@
</li>


<li class="nav-item current active">
<a class="nav-link nav-internal" href="#">
CI & Tests
<li class="nav-item">
<a class="nav-link nav-internal" href="../tests/index.html">
Tests
</a>
</li>

Expand Down Expand Up @@ -339,9 +338,9 @@
</li>


<li class="nav-item current active">
<a class="nav-link nav-internal" href="#">
CI & Tests
<li class="nav-item">
<a class="nav-link nav-internal" href="../tests/index.html">
Tests
</a>
</li>

Expand Down Expand Up @@ -492,15 +491,6 @@ <h1>CI and Testing - Coming Soon!<a class="headerlink" href="#ci-and-testing-com
<footer class="prev-next-footer">

<div class="prev-next-area">
<a class="left-prev"
href="../package-structure-code/code-style-linting-format.html"
title="previous page">
<i class="fa-solid fa-angle-left"></i>
<div class="prev-next-info">
<p class="prev-next-subtitle">previous</p>
<p class="prev-next-title">Python Package Code Style, Format and Linters</p>
</div>
</a>
</div>
</footer>

Expand Down
Loading

0 comments on commit c93824a

Please sign in to comment.