Skip to content
Closed
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
2 changes: 0 additions & 2 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ we pledge to follow the [University of Sheffield Research Software Engineering C

Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by following our [reporting guidelines][coc-reporting].
Please contact the [course organiser](mailto:liam.pattinson@york.ac.uk)
with any complaints.

[coc-reporting]: https://rse.shef.ac.uk/community/code_of_conduct#enforcement-guidelines
[coc]: https://rse.shef.ac.uk/community/code_of_conduct
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ use [GitHub flow][github-flow] to manage changes:

NB: The published copy of the lesson is usually in the `main` branch.

[repo]: https://github.com/researchcodingclub/python-testing-for-research
[repo-issues]: https://github.com/researchcodingclub/python-testing-for-research/issues
[contact]: mailto:liam.pattinson@york.ac.uk
[repo]: https://github.com/Romain-Thomas-Shef/FAIR_Management_plan
[repo-issues]: https://github.com/Romain-Thomas-Shef/FAIR_Management_plan/issues
[contact]: mailto:romain.thomas@sheffield.ac.uk
[github]: https://github.com
[github-flow]: https://guides.github.com/introduction/flow/
[github-join]: https://github.com/join
Expand Down
7 changes: 3 additions & 4 deletions LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ Attribution](https://creativecommons.org/licenses/by/4.0/) licence.
[Changes have been
made](https://github.com/RSE-Sheffield/fair4rs-lesson-setup) to adapt the
template to the specific context of the University of Sheffield's FAIR
for Research Software training programme, and altered further by
the University of York [Research Coding Club](https://researchcodingclub.github.io/).
for Research Software training programme.

Unless otherwise noted, the instructional material in this lesson is
made available under the [Creative Commons Attribution
Expand All @@ -36,7 +35,7 @@ Under the following terms:

- **Attribution**---You must give appropriate credit (mentioning that
your work is derived from work that is Copyright (c) The University
of York and, where practical, provide a [link to the
of Sheffield and, where practical, provide a [link to the
license][cc-by-human], and indicate if changes were made. You may do
so in any reasonable manner, but not in any way that suggests the
licensor endorses you or your use.
Expand All @@ -60,7 +59,7 @@ Except where otherwise noted, the example programs and other software
provided in this work are made available under the [OSI][osi]-approved
[MIT license][mit-license].

Copyright (c) The University of York
Copyright (c) The University of Sheffield

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
21 changes: 4 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ A short course on the basics of software testing in Python using the `pytest` li

This lesson uses [The Carpentries Workbench][workbench] template.

It is derived from the [FAIR2 for Research Software](https://fair2-for-research-software.github.io/)
training course [python-testing-for-research](https://github.com/FAIR2-for-research-software/python-testing-for-research)
by the University of Sheffield.

## Course Description

Whether you are a seasoned developer or just write the occasional script, it's important to know that your code does what you intend, and will continue to do so as you make changes.
Expand All @@ -22,7 +18,7 @@ This course seeks to provide you with conceptual understanding and the tools you
- Running a test suite & understanding outputs
- Best practices
- Testing for errors
- Testing floating point data
- Testing data structures
- Fixtures
- Parametrisation
- Testing file outputs
Expand All @@ -34,27 +30,18 @@ Contributions are welcome, please refer to the [contribution guidelines](CONTRIB

### Build the lesson locally

To render the lesson locally, you will need to have [R][r] installed.
Instructions for using R with the Carpentries template is available on the
[Carpentries website](https://carpentries.github.io/workbench/#installation).
We recommend using the
[`{renv}`](https://rstudio.github.io/renv/articles/renv.html) package.

After cloning the repository, you can set up `renv` and install all packages with:
To render the lesson locally, you will need to have [R][r] installed. Instructions for using R with the Carpentries template is [available](https://carpentries.github.io/workbench/#installation) but some additional setps have been taken to make sure the enivronment is reproducible using the [`{renv}`](https://rstudio.github.io/renv/articles/renv.html) package and an `renv.lockfile` is included which allows the environment to be re-created along with dependencies.

After cloning the repository, you can set up the `renv` and install all packages with:
``` r
renv::init()
renv::restore()
# Optionally update packages
renv::update()
```
Once you have installed the dependencies, you can render the pages locally by starting R in the project root and running:

``` r
sandpaper::serve()
```

When building the site subsequently, you may need to run `renv::activate()` first.

This will build the pages and start a local web-server in R and open it in your browser. These pages are "live" and will respond to local file changes if you save them.

[git]: https://git-scm.com
Expand Down
2 changes: 1 addition & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ episodes:
- 03-interacting-with-tests.Rmd
- 04-unit-tests-best-practices.Rmd
- 05-testing-exceptions.Rmd
- 06-floating-point-data.Rmd
- 06-testing-data-structures.Rmd
- 07-fixtures.Rmd
- 08-parametrization.Rmd
- 09-testing-output-files.Rmd
Expand Down
8 changes: 4 additions & 4 deletions episodes/00-introduction.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ exercises: 2
This course aims to equip researchers with the skills to write effective tests and ensure the quality and reliability of their research software. No prior testing experience is required! We'll guide you through the fundamentals of software testing using Python's Pytest framework, a powerful and beginner-friendly tool. You'll also learn how to integrate automated testing into your development workflow using continuous integration (CI). CI streamlines your process by automatically running tests with every code change, catching bugs early and saving you time. By the end of the course, you'll be able to write clear tests, leverage CI for efficient development, and ultimately strengthen the foundation of your scientific findings.

This course has a single continuous project that you will work on throughout the lessons and each lesson builds on the last through practicals that will help you apply the concepts you learn. However if you get stuck or fall behind during the course, don't worry!
All the stages of the project for each lesson are available in the `learners/files` directory in this [course's materials](https://github.com/researchcodingclub/python-testing-for-research) that you can copy across if needed. For example if you are on lesson 3 and haven't completed the practicals for lesson 2, you can copy the corresponding folder from the `learners/files` directory.
All the stages of the project for each lesson are available in the `files` directory in this course's materials that you can copy across if needed. For example if you are on lesson 3 and haven't completed the practicals for lesson 2, you can copy the corresponding folder from the `files` directory.

By the end of this course, you should:

Expand Down Expand Up @@ -72,9 +72,9 @@ This course uses blocks like the one below to indicate an exercise for you to at

::::::::::::::::::::::::::::::::::::: keypoints

- This course will teach you how to write effective tests and ensure the quality and reliability of your research software.
- No prior testing experience is required.
- You can catch up on practicals by copying the corresponding folder from the `learners/files` directory of this [course's materials](https://github.com/researchcodingclub/python-testing-for-research).
- This course will teach you how to write effective tests and ensure the quality and reliability of your research software
- No prior testing experience is required
- You can catch up on practicals by copying the corresponding folder from the `files` directory of this course's materials

::::::::::::::::::::::::::::::::::::::::::::::::

148 changes: 57 additions & 91 deletions episodes/01-why-test-my-code.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,16 @@ exercises: 2

## What is software testing?

Software testing is the process of checking that code is working as expected.
You may have data processing functions or automations that you use in your work.
How do you know that they are doing what you expect them to do?
Software testing is the process of checking that code is working as expected. You may have data processing functions or automations that you use in your work - how do you know that they are doing what you expect them to do?

Software testing is most commonly done by writing test code that check that
your code works as expected.
Software testing is most commonly done by writing code (tests) that check that your code works as expected.

This might seem like a lot of effort, so let's go over some of the reasons you
might want to add tests to your project.
This might seem like a lot of effort, so let's go over some of the reasons you might want to add tests to your project.


## Catching bugs

Whether you are writing the occasional script or developing a large software,
mistakes are inevitable. Sometimes you don't even know when a mistake creeps
into the code, and it gets published.
Whether you are writing the occasional script or developing a large software, mistakes are inevitable. Sometimes you don't even know when a mistake creeps into the code, and it gets published.

Consider the following function:

Expand All @@ -42,63 +36,50 @@ def add(a, b):
return a - b
```

When writing this function, I made a mistake. I accidentally wrote `a - b`
instead of `a + b`. This is a simple mistake, but it could have serious
consequences in a project.
When writing this function, I made a mistake. I accidentally wrote `a - b` instead of `a + b`. This is a simple mistake, but it could have serious consequences in a project.

When writing the code, I could have tested this function by manually trying it
with different inputs and checking the output, but:
When writing the code, I could have tested this function by manually trying it with different inputs and checking the output, but:

- This takes time.
- I might forget to test it again when we make changes to the code later on.
- Nobody else in my team knows if I tested it, or how I tested it, and
therefore whether they can trust it.
- Nobody else in my team knows if I tested it, or how I tested it, and therefore whether they can trust it.

This is where automated testing comes in.

## Automated testing

Automated testing is where we write code that checks that our code works as
expected. Every time we make a change, we can run our tests to automatically
make sure that our code still works as expected.
Automated testing is where we write code that checks that our code works as expected. Every time we make a change, we can run our tests to automatically make sure that our code still works as expected.

If we were writing a test from scratch for the `add` function, think for a
moment on how we would do it.

We would need to write a function that runs the `add` function on a set of
inputs, checking each case to ensure it does what we expect. Let's write a test
for the `add` function and call it `test_add`:
If we were writing a test from scratch for the `add` function, think for a moment on how we would do it.
We would need to write a function that runs the `add` function on a set of inputs, checking each case to ensure it does what we expect. Let's write a test for the `add` function and call it `test_add`:

```python
def test_add():
# Check that it adds two positive integers
if add(1, 2) != 3:
print("Test failed!")
# Check that it adds zero
if add(5, 0) != 5:
print("Test failed!")
# Check that it adds two negative integers
if add(-1, -2) != -3:
print("Test failed!")
# Check that it adds two positive integers
if add(1, 2) != 3:
print("Test failed!")
# Check that it adds zero
if add(5, 0) != 5:
print("Test failed!")
# Check that it adds two negative integers
if add(-1, -2) != -3:
print("Test failed!")
```

Here we check that the function works for a set of test cases. We ensure that
it works for positive numbers, negative numbers, and zero.
Here we check that the function works for a set of test cases. We ensure that it works for positive numbers, negative numbers, and zero.


::::::::::::::::::::::::::::::::::::: challenge

## What could go wrong?
## Challenge 1: What could go wrong?

When writing functions, sometimes we don't anticipate all the ways that they
could go wrong.
When writing functions, sometimes we don't anticipate all the ways that they could go wrong.

Take a moment to think about what is wrong, or might go wrong with these
functions:
Take a moment to think about what is wrong, or might go wrong with these functions:

```python
def greet_user(name):
return "Hello" + name + "!"
return "Hello" + name + "!"
```

```python
Expand All @@ -108,40 +89,38 @@ def gradient(x1, y1, x2, y2):

:::::::::::::::::::::::: solution

The first function will incorrectly greet the user, as it is missing a space
after "Hello". It would print `HelloAlice!` instead of `Hello Alice!`.

If we wrote a test for this function, we would have noticed that it was not
working as expected:
## Answer

The first function will incorrectly greet the user, as it is missing a space after "Hello". It would print `HelloAlice!` instead of `Hello Alice!`.

If we wrote a test for this function, we would have noticed that it was not working as expected:
```python
def test_greet_user():
if greet_user("Alice") != "Hello Alice!":
print("Test failed!")
if greet_user("Alice") != "Hello Alice!":
print("Test failed!")
```

The second function will crash if `x2 - x1` is zero.

If we wrote a test for this function, it may have helped us to catch this
unexpected behaviour:
If we wrote a test for this function, it may have helped us to catch this unexpected behaviour:

```python
def test_gradient():
if gradient(1, 1, 2, 2) != 1:
print("Test failed!")
if gradient(1, 1, 2, 3) != 2:
print("Test failed!")
if gradient(1, 1, 1, 2) != "Undefined":
print("Test failed!")
if gradient(1, 1, 2, 2) != 1:
print("Test failed!")
if gradient(1, 1, 2, 3) != 2:
print("Test failed!")
if gradient(1, 1, 1, 2) != "Undefined":
print("Test failed!")
```

And we could have amended the function:
And we could have ammened the function:

```python
def gradient(x1, y1, x2, y2):
if x2 - x1 == 0:
return "Undefined"
return (y2 - y1) / (x2 - x1)
if x2 - x1 == 0:
return "Undefined"
return (y2 - y1) / (x2 - x1)
```

:::::::::::::::::::::::::::::::::
Expand All @@ -150,72 +129,59 @@ def gradient(x1, y1, x2, y2):

## Finding the root cause of a bug

When a test fails, it can help us to find the root cause of a bug. For example,
consider the following function:
When a test fails, it can help us to find the root cause of a bug. For example, consider the following function:

```python

def multiply(a, b):
return a * a
return a * a

def divide(a, b):
return a / b
return a / b

def triangle_area(base, height):
return divide(multiply(base, height), 2)
return divide(multiply(base, height), 2)
```

There is a bug in this code too, but since we have several functions calling
each other, it is not immediately obvious where the bug is. Also, the bug is
not likely to cause a crash, so we won't get a helpful error message telling us
what went wrong. If a user happened to notice that there was an error, then we
would have to check `triangle_area` to see if the formula we used is right,
then `multiply`, and `divide` to see if they were working as expected too!
There is a bug in this code too, but since we have several functions calling each other, it is not immediately obvious where the bug is. Also, the bug is not likely to cause a crash, so we won't get a helpful error message telling us what went wrong. If a user happened to notice that there was an error, then we would have to check `triangle_area` to see if the formula we used is right, then `multiply`, and `divide` to see if they were working as expected too!

However, if we had written tests for these functions, then we would have seen
that both the `triangle_area` and `multiply` functions were not working as
expected, allowing us to quickly see that the bug was in the `multiply`
function without having to check the other functions.
However, if we had written tests for these functions, then we would have seen that both the `triangle_area` and `multiply` functions were not working as expected, allowing us to quickly see that the bug was in the `multiply` function without having to check the other functions.


## Increased confidence in code

When you have tests for your code, you can be more confident that it works as
expected. This is especially important when you are working in a team or
producing software for users, as it allows everyone to trust the code. If you
have a test that checks that a function works as expected, then you can be
confident that the function will work as expected, even if you didn't write it
yourself.
When you have tests for your code, you can be more confident that it works as expected. This is especially important when you are working in a team or producing software for users, as it allows everyone to trust the code. If you have a test that checks that a function works as expected, then you can be confident that the function will work as expected, even if you didn't write it yourself.

## Forcing a more structured approach to coding

When you write tests for your code, you are forced to think more carefully
about how your code behaves and how you will verify that it works as expected.
This can help you to write more structured code, as you will need to think
about how to test it as well as how it could fail.
When you write tests for your code, you are forced to think more carefully about how your code behaves and how you will verify that it works as expected. This can help you to write more structured code, as you will need to think about how to test it as well as how it could fail.

::::::::::::::::::::::::::::::::::::: challenge

## What could go wrong?
## Challenge 2: What could go wrong?

Consider a function that controls a driverless car.

- What checks might we add to make sure it is not dangerous to use?

```python

def drive_car(speed, direction):

... # complex car driving code
... # complex car driving code

return speed, direction, brake_status


```

:::::::::::::::::::::::: solution

## Answer

- We might want to check that the speed is within a safe range.
- We might want to check that the direction is a valid direction. ie not
towards a tree, and if so, the car should be applying the brakes.

- We might want to check that the direction is a valid direction. ie not towards a tree, and if so, the car should be applying the brakes.

:::::::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::
Expand Down
Loading