Skip to content

Commit ec0255c

Browse files
committed
Fix: edits from first pr applied
1 parent 83f4469 commit ec0255c

8 files changed

+332
-1
lines changed

Diff for: ci-tests-data/index.md

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Tests and data for your Python package
2+
3+
In this section you will learn more about the importance of writing
4+
tests for your Python package.
5+
6+
7+
:::{figure-md} fig-target
8+
9+
<img src="../images/packaging-lifecycle.png" alt="" width="800px">
10+
11+
Graphic showing the elements of the packaging process.
12+
:::
13+
14+
15+
16+
::::{grid} 1 1 3 3
17+
:class-container: text-center
18+
:gutter: 3
19+
20+
:::{grid-item-card}
21+
:link: write-tests
22+
:link-type: doc
23+
24+
✨ Why write tests ✨
25+
^^^
26+
27+
Learn more about the art of writing tests for your Python package.
28+
Learn about why you should write tests and how they can help you and
29+
potential contributors to your project.
30+
:::
31+
32+
:::{grid-item-card}
33+
:link: test-types
34+
:link-type: doc
35+
36+
✨ Types of tests ✨
37+
^^^
38+
There are three general types of tests that you can write for your Python
39+
package: unit tests, integration tests and end-to-end (or functional) tests. Learn about all three.
40+
:::
41+
42+
:::{grid-item-card}
43+
:link: run-tests
44+
:link-type: doc
45+
46+
✨ How to Run Your Tests ✨
47+
^^^
48+
49+
Learn more about what tools you can use to run tests. And how to run your
50+
tests on different Python versions and different operating systems both on
51+
your computer and using continuous integration on GitHub (or in any other CI).
52+
:::
53+
::::
54+
55+
```{toctree}
56+
:hidden:
57+
:maxdepth: 2
58+
:caption: Create & Run Tests
59+
60+
Intro <self>
61+
Write tests <write-tests>
62+
Test types <test-types>
63+
Run tests <run-tests>
64+
Code coverage <code-cov>
65+
66+
```
67+
68+
```{toctree}
69+
:hidden:
70+
:maxdepth: 2
71+
:caption: Continuous Integration
72+
73+
Intro to CI <ci>
74+
Run tests in CI <tests-ci>
75+
76+
```
77+
78+
```{toctree}
79+
:hidden:
80+
:maxdepth: 2
81+
:caption: Package data
82+
83+
Package data <data>
84+
85+
```

Diff for: ci-tests-data/test-types.md

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Test Types for Python packages
2+
3+
_Unit, Integration & Functional Tests_
4+
5+
There are different types of tests that you want to consider when creating your
6+
test suite:
7+
8+
1. Unit tests
9+
2. Integration
10+
3. End-to-end (also known as Functional) tests
11+
12+
Each type of test has a different purpose. Here, you will learn about all three types of tests.
13+
14+
```{admonition}
15+
I think this page would be stronger if we did have some
16+
examples from our package here: https://github.com/pyOpenSci/pyosPackage
17+
18+
19+
```
20+
21+
## Unit Tests
22+
23+
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.
24+
25+
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.
26+
27+
**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.
28+
29+
```python
30+
# Example package function
31+
def celsius_to_fahrenheit(celsius):
32+
"""
33+
Convert temperature from Celsius to Fahrenheit.
34+
35+
Parameters:
36+
celsius (float): Temperature in Celsius.
37+
38+
Returns:
39+
float: Temperature in Fahrenheit.
40+
"""
41+
fahrenheit = (celsius * 9/5) + 32
42+
return fahrenheit
43+
```
44+
45+
Example unit test for the above function. You'd run this test using the `pytest` command in your **tests/** directory.
46+
47+
```python
48+
import pytest
49+
from temperature_converter import celsius_to_fahrenheit
50+
51+
def test_celsius_to_fahrenheit():
52+
"""
53+
Test the celsius_to_fahrenheit function.
54+
"""
55+
# Test with freezing point of water
56+
assert pytest.approx(celsius_to_fahrenheit(0), abs=0.01) == 32.0
57+
58+
# Test with boiling point of water
59+
assert pytest.approx(celsius_to_fahrenheit(100), abs=0.01) == 212.0
60+
61+
# Test with a negative temperature
62+
assert pytest.approx(celsius_to_fahrenheit(-40), abs=0.01) == -40.0
63+
64+
```
65+
66+
```{figure} ../images/pyopensci-puzzle-pieces-tests.png
67+
:height: 300px
68+
:alt: image of puzzle pieces that all fit together nicely. The puzzle pieces are colorful - purple, green and teal.
69+
70+
Your unit tests should ensure each part of your code works as expected on its own.
71+
```
72+
73+
### Integration tests
74+
75+
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.
76+
77+
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.
78+
79+
```python
80+
81+
def fahr_to_celsius(fahrenheit):
82+
"""
83+
Convert temperature from Fahrenheit to Celsius.
84+
85+
Parameters:
86+
fahrenheit (float): Temperature in Fahrenheit.
87+
88+
Returns:
89+
float: Temperature in Celsius.
90+
"""
91+
celsius = (fahrenheit - 32) * 5/9
92+
return celsius
93+
94+
# Function to calculate the mean temperature for each year and the final mean
95+
def calc_annual_mean(df):
96+
# TODO: make this a bit more robust so we can write integration test examples??
97+
# Calculate the mean temperature for each year
98+
yearly_means = df.groupby('Year').mean()
99+
100+
# Calculate the final mean temperature across all years
101+
final_mean = yearly_means.mean()
102+
103+
# Return a converted value
104+
return fahr_to_celsius(yearly_means), fahr_to_celsius(final_mean)
105+
106+
```
107+
108+
```{figure} ../images/python-tests-puzzle.png
109+
:height: 350px
110+
: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.
111+
112+
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.
113+
114+
```
115+
116+
```{figure} ../images/python-tests-puzzle-fit.png
117+
:height: 450px
118+
:alt: image of puzzle pieces that all fit together nicely. The puzzle pieces are colorful - purple, green and teal.
119+
120+
Your integration tests should ensure that parts of your code that are expected to work
121+
together, do so as expected.
122+
123+
```
124+
125+
### End-to-end (functional) tests
126+
127+
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.
128+
129+
```{figure} ../images/flower-puzzle-pyopensci.jpg
130+
:height: 450px
131+
:alt: Image of a completed puzzle showing a daisy
132+
133+
End-to-end or functional tests represent an entire workflow that you
134+
expect your package to support.
135+
136+
```
137+
138+
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.
139+
140+
```{note}
141+
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.
142+
```
143+
144+
### Comparing unit, integration and end-to-end tests
145+
146+
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.
147+
148+
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
149+
break. But if the refactor didn't introduce new behavior to your existing
150+
code, then you can rely on your unit tests to continue to pass, testing the
151+
original functionality of your code.
152+
153+
It is important to note that you don't need to spend energy worrying about
154+
the specifics surrounding the different types of tests. When you begin to
155+
work on your test suite, consider what your package does and how you
156+
may need to test parts of your package. Bring familiar with the different types of tests can provides a framework to
157+
help you think about writing tests and how different types of tests can complement each other.

Diff for: ci-tests-data/write-tests.md

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Write tests for your Python package
2+
3+
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
4+
that are written with the intention of making sure a specific part of your code
5+
works as you expected it to.
6+
7+
## Why write tests for your package?
8+
9+
Tests act as a safety net for code changes. They help you spot and rectify bugs
10+
before they affect users. Tests also instill confidence that code alterations from
11+
contributors won't breaking existing functionality.
12+
13+
Writing tests for your Python package is important because:
14+
15+
- **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.
16+
- **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.
17+
- **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.
18+
- **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.
19+
- **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.
20+
- **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.
21+
- **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.
22+
23+
### Tests for user edge cases
24+
25+
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
26+
your package's functionality. For example, what occurs if a function expects a
27+
pandas `dataframe` but a user supplies a numpy `array`? Does your code gracefully
28+
handle this situation, providing clear feedback, or does it leave users
29+
frustrated by an unexplained failure?
30+
31+
:::{note}
32+
33+
For a good introduction to testing, see [this Software Carpentry lesson](https://swcarpentry.github.io/python-novice-inflammation/10-defensive.html)
34+
35+
:::
36+
37+
```{figure} ../images/python-tests-puzzle.png
38+
:height: 350px
39+
40+
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.
41+
42+
```
43+
44+
````{admonition} Test examples
45+
:class: note
46+
47+
Let’s say you have a Python function that adds two numbers a and b together.
48+
49+
```python
50+
def add_numbers(a, b):
51+
return a + b
52+
```
53+
54+
A test to ensure that function runs as you might expect when provided with different numbers might look like this:
55+
56+
```python
57+
def test_add_numbers():
58+
result = add_numbers(2, 3)
59+
assert result == 5, f"Expected 5, but got {result}"
60+
61+
result2 = add_numbers(-1, 4)
62+
assert result2 == 3, f"Expected 3, but got {result2}"
63+
64+
result3 = add_numbers(0, 0)
65+
assert result3 == 0, f"Expected 0, but got {result3}"
66+
67+
test_add_numbers()
68+
69+
```
70+
````
71+
72+
🧩🐍
73+
74+
### How do I know what type of tests to write?
75+
76+
:::{note}
77+
This section has been adapted from [a presentation by Nick Murphy](https://zenodo.org/record/8185113).
78+
:::
79+
80+
At this point, you may be wondering - what should you be testing in your package? Below are a few examples:
81+
82+
- **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.
83+
84+
- **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
85+
86+
* **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)..
87+
88+
* **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 for: images/flower-puzzle-pyopensci.jpg

270 KB
Loading

Diff for: images/packaging-lifecycle.png

295 KB
Loading

Diff for: images/pyopensci-puzzle-pieces-tests.png

109 KB
Loading

Diff for: images/python-tests-puzzle.png

1.08 MB
Loading

Diff for: index.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ Packaging <package-structure-code/intro>
2020
:hidden:
2121
:caption: CI and Testing
2222
23-
CI & Tests <ci-and-testing/intro>
23+
CI & Tests <ci-tests-data/index>
24+
2425
```
2526

2627
<!-- Github community standards

0 commit comments

Comments
 (0)