<!-- .slide: data-state="title" -->
# Testing
note:
===
<!-- .slide: data-state="standard" -->
## Why Test?
<ul>
<li>Preserve functionality
<ul>
<li>Detect new errors early</li>
<li>Facilitate reproducibility for research software</li>
</ul></li>
<li class="fragment">Help users
<ul>
<li>Verify correct installation</li>
<li>Improve correctness for research output</li>
</ul></li>
<li class="fragment">Enable developers
<ul>
<li>Make refactoring easier</li>
<li>Simplify external contributions</li>
</ul></li>
</ul>
<h3 style="margin-top: 1em;" class="fragment">š§® Manage Complexity š§©</h3>
===
<!-- .slide: data-state="standard" -->
## Test Types
<ul>
<li>Unit test
<ul class="fragment fade-up" data-fragment-index="1">
<li>Smallest possible unit</li>
<li>No dependency on outside code...</li>
<li>(... replace them with mocks, stubs, etc.)</li>
</ul></li>
</ul>
<ul class="fragment fade-up" data-fragment-index="2">
<li>Integration test
<ul class="fragment fade-up" data-fragment-index="3">
<li>Test unit interaction</li>
<li>Can be on small scales, or system wide</li>
</ul></li>
</ul>
<div class="fragment fade-up" data-fragment-index="4" style='position:relative; padding: 0 0 calc(55.00% + 44px) 0; margin: -9em auto 0 auto;'></div><p style="font-size: large; margin: 0; padding: 0;"> </p>
===
<!-- .slide: data-state="standard" -->
## How much testing is enough?
Test metrics:
- lines of code : lines of tests (target: 1:3)
- test coverage [example](https://sonarcloud.io/component_measures?id=eWaterCycle_ewatercycle&metric=coverage&view=treemap&selected=eWaterCycle_ewatercycle%3Aewatercycle) (target: >= 80%)
Targets are defined as *necessary*, but *not sufficient* goals.
===
<!-- .slide: data-state="standard" -->
# PyTest
- recommended python testing framework
- [docs.pytest.org](https://docs.pytest.org/en/7.3.x/)
![](.files/pytest_logo.svg)
===
<!-- .slide: data-state="standard" -->
## Write Code
<pre><code class="bash" style="overflow: hidden;" data-trim class="bash" data-line-numbers>
$ mkdir pytest-example
$ cd pytest-example
</code></pre>
<div class="fragment">
Creating a file <code>example.py</code> containing
<pre><code class="python" style="overflow: hidden;" data-trim class="bash" data-line-numbers>
def add(a, b):
return a + b
def test_add(): # Special name!
assert add(2, 3) == 5 # What's `assert`? š¤
assert add('space', 'ship') == 'spaceship'
</code></pre>
</div>
<div class="fragment">
Chat with the python shell about <code>assert</code> ...
</div>
<div class="fragment">
<pre><code class="python" style="overflow: hidden;" data-trim class="bash" data-line-numbers>
>>> assert 1==1 # passes
>>> assert 1==2 # throws error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
</code></pre>
</div>
===
<!-- .slide: data-state="standard" -->
## Test!
<pre><code style="overflow: hidden;" data-trim class="bash" data-line-numbers="1|1-9">
$ pytest example.py
======================== test session starts ========================
platform linux -- Python 3.6.9, pytest-7.0.1, pluggy-1.0.0
rootdir: /home/ole/Desktop/pytest-texample
collected 1 item
example.py . [100%]
========================= 1 passed in 0.00s =========================
</code></pre>
===
<!-- .slide: data-state="standard" -->
## Breaking Things
<pre><code class="python" style="overflow: hidden;" data-trim class="bash" data-line-numbers>
def add(a, b):
return a - b # Uh oh, mistake! š±
def test_add():
assert add(2, 3) == 5
assert add('space', 'ship') == 'spaceship'
</code></pre>
===
<!-- .slide: data-state="standard" -->
## Testing Again
<pre><code style="overflow: hidden;" data-trim class="bash" data-line-numbers="1|2-8|9-17|18-20">
$ pytest example.py
======================== test session starts =========================
platform linux -- Python 3.6.9, pytest-7.0.1, pluggy-1.0.0
rootdir: /home/ole/Desktop/pytest-texample
collected 1 item
example.py F [100%]
============================== FAILURES ==============================
______________________________ test_add ______________________________
def test_add():
> assert add(2, 3) == 5
E assert -1 == 5
E + where -1 = add(2, 3)
example.py:6: AssertionError
====================== short test summary info =======================
FAILED example.py::test_add - assert -1 == 5
========================= 1 failed in 0.05s ==========================
</code></pre>
<ul>
<li class="fragment">šā<span class="fragment">Functions fail on first error</span></li>
<li class="fragment">But all test functions are executed</li>
</ul>
===
<!-- .slide: data-state="standard" -->
## Take-away
- pytest collects and runs all test functions starting with <code>test_</code>
- The tests pass when they do not throw (assertion) errors
<pre style="width: max-content;"><code style="overflow: hidden;" class="python" data-trim class="bash" data-line-numbers>
def steal_sheep():
...
def paint_cows():
...
# optionally in another file:
def test_steal_sheep():
...
def test_paint_cows():
...
</code></pre>
===
<!-- .slide: data-state="standard" -->
# Recap: pure functions
<div style="width: 59%; float: left;">
<ul style="margin-top: 1ex;">
<li>Are deterministic</li>
<li>Have a return value</li>
<li>Have no side effects<sup>[1]</sup></li>
<li>Have referential transparency<sup>[2]</sup></li>
<ul>
</div>
<div style="width: 39%; float: right;">
<pre class="fragment" style="width: max-content;" data-id="code-animation"><code class="python" style="overflow: hidden; padding-left: 1em; padding-right: 1em;" data-trim data-noescape class="bash" data-line-numbers="1-2|1-6|4-8">
def last(my_array):
return my_array[-1]
def add(a, b):
return a + b
print(add(1, 2))
print(3)
</code></pre>
</div>
<h4 class="fragment" style="width: 100%; float: left; margin-top: 1em;">Pure functions are easy to understand and test!</h4>
<footer>
[1] Side effects: interactions of a function with its surroundings
<br>
[2] Replacing a function call with the return of that function should not change anything
</footer>
===
<!-- .slide: data-state="standard" -->
## Take-away
- Use pure functions when possible š
- Testing does not have to be hard š
- You test anyways, but then throw the test away š§
- You don't have to strive for šÆ% test coverage
- Aim for a balance between unit- and integration tests āļø
- Testing removes the dread of refactoring š
- Your future you (and others!) will thank you š
===
<!-- .slide: data-state="standard" -->
# Test-Driven Development: FizzBuzz Function
<div class="r-stack">
<img src="./media/testing/fizz_buzz_1.svg">
<img class="fragment" data-fragment-index="1" src="./media/testing/fizz_buzz_2.svg">
<img class="fragment" data-fragment-index="2" src="./media/testing/fizz_buzz_3.svg">
<img class="fragment" data-fragment-index="3" src="./media/testing/fizz_buzz_4.svg">
<img class="fragment" data-fragment-index="4" src="./media/testing/fizz_buzz_5.svg">
<img class="fragment" data-fragment-index="5" src="./media/testing/fizz_buzz_6.svg">
</div>
<ul>
<li>fizz_buzz() takes an integer argument and returns it, BUT</li>
<ul>
<li class="fragment" data-fragment-index="1">fails on zero or negative numbers</li>
<li class="fragment" data-fragment-index="2">instead returns "Fizz" on multiples of 3</li>
<li class="fragment" data-fragment-index="3">instead returns "Buzz" on multiples of 5</li>
<li class="fragment" data-fragment-index="5">instead returns "FizzBuzz" on multiples of 3 and 5</li>
</ul>
</ul>
===
<!-- .slide: data-state="standard" -->
## FizzBuzz Function
<ul>
<li>fizz_buzz() takes an integer argument and returns it, BUT</li>
<ul>
<li>fails on zero or negative numbers</li>
<li>instead returns "Fizz" on multiples of 3</li>
<li>instead returns "Buzz" on multiples of 5</li>
<li>instead returns "FizzBuzz" on multiples of 3 and 5</li>
</ul>
<li class="fragment">Create an empty function fizz_buzz()</li>
<li class="fragment">Write the tests</li>
<li class="fragment">Paste your tests in the collab document, and discuss</li>
<li class="fragment">Now write a function code to make your tests pass</li>
</ul>
===
<!-- .slide: data-state="standard" -->
## Take-away
- What did you think of this style of development?
- Was it easier or harder than just writing code?
- Would your code look different without the tests? <!-- .element class="fragment" -->
- For what kind of projects would it be (not) useful? <!-- .element class="fragment" -->
<div class="fragment" style="width: 20vw; margin: 1em auto;">Test-Driven Development (TDD) is an optional tool in your toolbox š ļø</div>