Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions exercises/problem/climate_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
solar_constant = 1370
sigma = 5.67e-8 # Stefan-Boltzmann constant

def absorbed_solar_radiation(albedo):
return solar_constant*(1-albedo)/4

def emitted_radiation(emissivity, temperature):
return emissivity * sigma * temperature**4

def energy_balanced_temperature(emissivity, albedo):
# dT/dt = 0 => in = out
return (solar_constant*(1-albedo)/(4*emissivity * sigma))**0.25

def emissivity_given_temp(albedo, temperature):
return solar_constant*(1-albedo)/(4*sigma*temperature**4)
25 changes: 25 additions & 0 deletions exercises/problem/plot4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import climate_model

import xarray
import matplotlib.pyplot as plt

global_ds = xarray.open_dataset("../data/HadCRUT.5.0.0.0_analysis_summary-series_global_annual.nc")

fig, axs = plt.subplots(1, 1, figsize=(10,6), layout='constrained')

# Absolute mean temperature between 1961 and 1990
# https://crudata.uea.ac.uk/cru/data/temperature/abs_glnhsh.txt
absolute_value = 13.974 + 273.5
time = global_ds['time'].data
temperature = global_ds['tas_mean'].data + absolute_value
albedo = 0.3

epsilon = climate_model.emissivity_given_temp(albedo, temperature)

axs.plot(time, epsilon)

axs.set_xlabel("Year")
axs.set_ylabel("Effective emissivity of Earth")

plt.show()

13 changes: 13 additions & 0 deletions exercises/testing_exercises.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Exercises

In the example repository, there is a file called 'climate_model.py'.
This file contains some functions that are used in 'plot4.py'

Task:

1. Write some basic tests for these functions (think about inputs that give known
outputs)
2. Think about what properties/behaviours these functions should have, and write
tests for them
3. Look at the other python files, and see if any functions can be extracted and
tested
162 changes: 162 additions & 0 deletions src/_pytest.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Testing with pytest

## What is pytest?

:::{.columns}
::::{.column width=50%}
- A testing *framework* for python
- Makes it a lot easier to write tests
- Is used extensively by python packages
- Automatically discovers and runs tests
::::
::::{.column width=50%}
![](img/pytest.png){style="width:50%;"}
::::
:::

## pytest basics

\

- Tests are .py files and live in a `tests/` directory
- Each test group is a function with prefix `test_`
- Use the `assert` keyword to validate an expression is true

\

:::{.columns}
::::{.column width=30%}
```{.python filename="src/add.py"}
def add(a, b):
return a+b
```
```{.python filename="tests/test.py"}
def test_add():
assert add(0, 0) == 0
assert add(1, 0) == 1
assert add(0, 1) == 1
assert add(1, 1) == 2
...
```
::::
::::{.column width=70%}

```{=html}
<div class="code-with-filename-file"><pre><strong>terminal</strong></pre></div>
<div class="term-container">
<span class="term-fg1">===================================== test session starts =====================================</span>
platform linux -- Python 3.14.7, pytest-9.1.1, pluggy-1.6.0
rootdir: &#47;tmp&#47;example
<span class="term-fg1">collected 1 item </span>
&nbsp;
tests&#47;test.py <span class="term-fg32">. [100%]</span>
&nbsp;
<span class="term-fg32">====================================== </span><span class="term-fg32 term-fg1">1 passed</span><span class="term-fg32"> in 0.00s ======================================</span>
</div>
```
::::
:::

## pytest basics

What if we implemented `add` wrong?

```{.python filename="src/add,py"}
def add(a, b):
return a-b # Incorrect
```

\

```{=html}
<div class="term-container">
<span class="term-fg32 term-fg1">[jack@nixos:~]$</span> pytest tests&#47;test.py
platform linux -- Python 3.14.7, pytest-9.1.1, pluggy-1.6.0
rootdir: &#47;tmp&#47;example
<span class="term-fg1">collected 1 item </span>
&nbsp;
tests&#47;test.py <span class="term-fg31">F [100%]</span>
&nbsp;
========================================== FAILURES ===========================================
<span class="term-fg31 term-fg1">__________________________________________ test_add ___________________________________________</span>
&nbsp;
<span class="term-fgi94">def</span><span class="term-fgi90"> </span><span class="term-fgi92">test_add</span>():
<span class="term-fgi94">assert</span> add(<span class="term-fgi94">0</span>, <span class="term-fgi94">0</span>) == <span class="term-fgi94">0</span>
<span class="term-fgi94">assert</span> add(<span class="term-fgi94">1</span>, <span class="term-fgi94">0</span>) == <span class="term-fgi94">1</span>
&gt; <span class="term-fgi94">assert</span> add(<span class="term-fgi94">0</span>, <span class="term-fgi94">1</span>) == <span class="term-fgi94">1</span>
<span class="term-fg31 term-fg1">E assert -1 == 1</span>
<span class="term-fg31 term-fg1">E + where -1 = add(0, 1)</span>
&nbsp;
<span class="term-fg31 term-fg1">tests&#47;test.py</span>:7: AssertionError
<span class="term-fg36 term-fg1">=================================== short test summary info ===================================</span>
<span class="term-fg31">FAILED</span> tests&#47;test.py::<span class="term-fg1">test_add</span> - assert -1 == 1
<span class="term-fg31">====================================== </span><span class="term-fg31 term-fg1">1 failed</span><span class="term-fg31"> in 0.02s ======================================</span>
</div>
```

## Parametrized tests

- pytest implements a lot of useful tools to make writing tests easier
- One of these is test parametrization, which allows us to reuse tests for different
inputs
- We only need to give the `pytest.mark.parametrize` decorator:
- The names of the input variables
- A list of tuples of inputs

```{.python filename="tests/test.py"}
@pytest.mark.parametrize("x,y,expected", [(0, 0, 0), (1, 0, 1), (0, 1, 1), (1, 1, 2)])
def test_add(x, y, expected):
assert add(x, y) == expected
```

## What to test

- Beyond using fixed points, we can think about how a function should behave
- For example, our add function should have the following *properties*
- `add(x,0) == x`
- `add(x, y) == add(y, x)`
- `add(x, add(y, z)) == add(add(x, y), z)`

## Writing code for unit testing

\

- Unit testing forces us to write code that is minimal
- Decompose functions into their smallest logical unit
- Smaller functions are easier to test (and review!)
- Fewer/simpler inputs
- Can help to think of edge cases

### Example

:::{.columns}
::::{.column width=50%}
Easier to test this:
```{.python}
def complicated_computation(data):
z = computation_1(data.x, data.y)
return computation_2(data.t, data.w, z)

def computation_1(x, y):
result = atan2(x, y)
# Maybe do something else here
return result

def computation_2(t, w, z):
result = exp((w+z)/t)
# Maybe do something else here
return result
```
::::
::::{.column width=50%}
Than this:
```{.python}
def complicated_computation(data):
z = atan2(data.x, data.y)
z = ...
result = exp((data.w+z)/data.t)
result = ...
return result
```
::::
:::
Loading