# Coderefinery exercises TUSAIL workshop 2023-05-09
## Day 2 Collaborative distributed version control
### Exercise: create a remote repository
- Create a repository in the TUSAIL organization on GitHub
- Name it following the convention: ESR{n}_LastName_CR (so, if you are called "Barbara Vreede" and your ESR number is 7, then the repository is called ESR07_Vreede_CR).
- Do NOT add a Readme, License, or any other documents!
- Use the instructions on GitHub to connect your local repository to this remote repository on GitHub
- Paste the link to your final repository below.
### Exercise: Working as a project collaborator (in breakout rooms)
Do this exercise in pairs; make sure you have worked both as person A and as person B.
#### Part 1
- PERSON A: Create an issue in their repository
- PERSON B: Clone this repository to their system
- PERSON B: Create a new branch
- PERSON B: Make the changes requested in the issue
- PERSON B: Push the changes to the remote repository on GitHub
- PERSON B: submit a Pull Request, refer to the issue (e.g. "Closes #1")
#### Part 2
- PERSON A: review the Pull Request
- PERSON B: address the comments
- PERSON A: approve the Pull Request
- PERSON B: merge the Pull Request
### Exercise: Working as an external contributor (in breakout rooms)
#### Part 1
- PERSON A: Create an issue in Person B's repository
- PERSON A: Fork the repository to their own (= Person A's) account
- PERSON A: Clone the repository, make changes, push them back to the fork
- PERSON A: Submit a Pull Request from the fork to the original repository
#### Part 2
- PERSON B: Make a change in the original repository in the same place as person A's proposed changes
- PERSON A: Solve the merge conflict in the Pull Request
- PERSON B: Review/Approve the Pull Request
- PERSON B: merge the Pull Request
## Day 3
## Modularity
Extract functions from the following (terrible) code:
```python=
def convert_temperature(temperature, unit):
if unit == "F":
# Convert Fahrenheit to Celsius
celsius = (temperature - 32) * (5 / 9)
if celsius < -273.15:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
# Convert Celsius to Kelvin
kelvin = celsius + 273.15
if kelvin < 0:
# Invalid temperature, below absolute zero
if temperature % 2 == 0:
return "Invalid temperature"
else:
if kelvin % 2 == 0:
return "Invalid temperature"
else:
return celsius, kelvin
else:
if celsius % 2 == 0:
# Convert Celsius to Fahrenheit
fahrenheit = (celsius * (9 / 5)) + 32
if fahrenheit < -459.67:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
if fahrenheit % 2 == 0:
return fahrenheit, kelvin
else:
return fahrenheit, celsius
else:
return celsius, kelvin
elif unit == "C":
# Convert Celsius to Fahrenheit
fahrenheit = (temperature * (9 / 5)) + 32
if fahrenheit < -459.67:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
# Convert Celsius to Kelvin
kelvin = temperature + 273.15
if kelvin < 0:
# Invalid temperature, below absolute zero
if temperature % 2 == 0:
return "Invalid temperature"
else:
if kelvin % 2 == 0:
return "Invalid temperature"
else:
return fahrenheit, kelvin
else:
if temperature % 2 == 0:
# Convert Celsius to Fahrenheit
fahrenheit = (temperature * (9 / 5)) + 32
if fahrenheit < -459.67:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
if fahrenheit % 2 == 0:
return fahrenheit, kelvin
else:
return fahrenheit, temperature
else:
return fahrenheit, kelvin
elif unit == "K":
# Convert Kelvin to Celsius
celsius = temperature - 273.15
if celsius < -273.15:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
# Convert Celsius to Fahrenheit
fahrenheit = (celsius * (9 / 5)) + 32
if fahrenheit < -459.67:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
if celsius % 2 == 0:
# Convert Celsius to Fahrenheit
fahrenheit = (celsius * (9 / 5)) + 32
if fahrenheit < -459.67:
# Invalid temperature, below absolute zero
return "Invalid temperature"
else:
if fahrenheit % 2 == 0:
return fahrenheit, celsius
else:
return fahrenheit, kelvin
else:
return celsius, fahrenheit
else:
return "Invalid unit"
```
## Day 3: Documentation lesson
### Exercise: Think of good and bad examples
Write down your thoughts in the collaborative documents.
Respond with emojis :+1: :scream_cat: to your colleagues' answers.
- Think of projects of which you like the documentation. What do you like about them?
- Think of projects for which you don’t like the documentation. What don’t you like about them? Are you missing anything?
**NB: You can choose a mature library with lots of users for this exercise, but try to also think of less mature projects you had to collaborate on, or papers you had to reproduce.**
### Exercise: Writing good comments
Let's take a look at two example comments (comments in python start with `#`):
#### Comment A
```python
# Now we check if temperature is larger than -50:
if temperature > -50:
print('do something')
```
#### Comment B
```python
# We regard temperatures below -50 degrees as measurement errors
if temperature > -50:
print('do something')
```
Which of these comments is best? Can you explain why?
Write your answer in the collaborative document (before looking at others' answers)
### Exercise: Adding in-code documentation
Update this code snippet so it is well-documented:
```python
import pandas as pd
def x(a, print_columns=False):
b = pd.read_excel(a)
column_headers = list(b.columns.values)
if print_columns:
print("\n".join(column_headers))
return column_headers
```
1. One person screen-shares, discuss and together converge on a final solution. (8 minutes)
2. Share final solution in collaborative document.
### Exercise: Write a README file
You are going to write a README file for your project.
1. One person shares the screen and is the driver. The others are 'navigators' and tell the 'driver' what to do.
1. Fork the project
2. Add a file called `README.md` (you can use the github web interface or work locally (i.e. `git clone`, edit the file, `git add`, `git commit`, `git push`))
3. Add some content to your README file. Think about what you want the audience to know about your project!
It does not matter whether the information is correct, it is more important that you have all the components that make up a good README file.
4. Note that the README file is nicely rendered on the github repository page.
NB: The `README.md` file is written in 'Markdown' a very popular lightweight markup language, all you need to know for now is this syntax:
```
# A section title
## A subsection title
Normal text
A list with items
- item1
- item2
```
(Optional): Use [https://hemingwayapp.com/](https://hemingwayapp.com/) to analyse your README file and make your writing bold and clear!
### Exercise: Sphinx content
1. Add a entry below feature-a.md labeled *feature-b.md* to the `index.rst` file.
2. Create a file `feature-b.md` in the same directory as your `feature-a.md` file.
3. Add some content to feature-b, rebuild with `sphinx-build`, and refresh the browser to look at the results
([Help](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html)).
Experiment with the following Markdown syntax:
- \*Emphasized text\* and \*\*bold text\*\*
- Headings
```md
# Level 1
## Level 2
### Level 3
#### Level 4
```
- An image: ``
- `[A link](http://www.google.com)`
- Numbered lists (numbers adjusted automatically)
```md
1. item 1
2. item 2
3. item 3
1. item 4
1. item 5
```
- Simple tables
```md
| No. | Prime |
| ---- | ------ |
| 1 | No |
| 2 | Yes |
| 3 | Yes |
| 4 | No |
```
- Code block
~~~md
The following is a code block:
```
def hello():
print("Hello world")
```
~~~
- Code block specifying syntax highlighting for other language than Python
~~~md
```c
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
```
~~~
- You could include the contents of an external file using `{include}` directive, as follows:
~~~md
```{include} ../README.md
```
~~~
<!--
Note, that this will not resolve e.g. image paths within README.md, use experimental feature `{literalinclude}` instead:
~~~md
```
{literalinclude} ../README.md
:language: md
```
~~~
-->
- It is possible to combine `{include}` with code highlighting, line numbering, and even line highlighting.
- We can also use jupyter notebooks (*.ipynb) with sphinx. It requires `nbsphinx` extension to be installed. See [nbsphinx documentation](http://nbsphinx.readthedocs.io/en/latest/) for more information
```rst
.. toctree::
:maxdepth: 2
:caption: Contents:
feature-a.md
<python_notebook_name>.ipynb
```
### Exercise: Github Pages (15 minutes)
- Deploy own website reusing a template:
- Follow <https://pages.github.com/>
- Select "Project site"
- Select "Choose a theme" (for instance "Minimal")
- Click "Select theme"
- Adjust the README.md and commit
- Browse your page on `http://username.github.io/repository` (adjust "username" and "repository")
- Make a change to the repository after the webpage has been deployed for the first time
- Verify that the change shows up on the website a minute or two later
The documentation for GitHub Pages is very good so no need for us to duplicate
screenshots: <https://pages.github.com/>
> #### Real-life examples
>
> - Research Software Hour
> - Source: <https://raw.githubusercontent.com/ResearchSoftwareHour/researchsoftwarehour.github.io/main/content/about.md>
> - Result: <https://researchsoftwarehour.github.io/about/>
> - This lesson
> - Source: <https://raw.githubusercontent.com/coderefinery/documentation/gh-pages/_episodes/06-gh-pages.md>
> - Result: this page
Paste a link to your github page in the collaborative document.
### Recap modular coding, documentation + collaborative Git + Github
* What questions do you still have?
* Whether there are any incremental improvements that can benefit your projects?
* What’s nice that we learnt but is overkill for your current work?
## Day 4: Testing lesson
<!-- where does this come from?
### Exercise: write a test for the following function:
```
def create_name(num):
name = f"plot_{str(num)}.png"
return name
```
- use multiple different inputs, and verify their output
- write different kinds of assertions
- include a test for an error
### If you have time left and want a challenge:
#### Bonus exercise 1:
Write a test for any of the other functions in the codebase
#### Bonus exercise 2:
Write a failing test. Then improve the code so it no longer fails.
Please share in your answer below not just the (no longer failing) test, but also the updated code!
### Exercise : Discussion
Why do you write tests/ Why do you not write tests? Write reasons into the collabroative document.
### Excercise: Testing locally (15min)
Please use https://coderefinery.github.io/testing/pytest/
### Excercise: Testing as part of the CI (15min)
Add the following to your github actions workflow file from yesterday.
```
- name: Test with pytest
run: |
pytest example.py
```
Add these functions to your ``example.py``:
```python=
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add('space', 'ship') == 'spaceship'
def subtract(a, b):
return a + b # <--- fix this
# uncomment the following test
#def test_subtract():
# assert subtract(2, 3) == -1
```
See what the CI does now and then uncomment the ``test_substract`` test.See what the CI does now, Afterwards fix the ``subtract`` function.
-->
### Exercise : Test design (35min)
Pick one function from the following functions/classes. Discuss how you would test it. If you have time, write tests for it and also test the error condition.
```python
#1
def factorial(n):
"""
Computes the factorial of n.
"""
if n < 0:
raise ValueError('received negative input')
result = 1
for i in range(1, n + 1):
result *= i
return result
# 2
def count_word_occurrence_in_string(text, word):
"""
Counts how often word appears in text.
Example: if text is "one two one two three four"
and word is "one", then this function returns 2
"""
words = text.split()
return words.count(word)
# 3
def count_word_occurrence_in_file(file_name, word):
"""
Counts how often word appears in file file_name.
Example: if file contains "one two one two three four"
and word is "one", then this function returns 2
"""
count = 0
with open(file_name, 'r') as f:
for line in f:
words = line.split()
count += words.count(word)
return count
# 4
def check_reactor_temperature(temperature_celsius):
"""
Checks whether temperature is above max_temperature
and returns a status.
"""
from reactor import max_temperature
if temperature_celsius > max_temperature:
status = 1
else:
status = 0
return status
# 5
class Pet:
def __init__(self, name):
self.name = name
self.hunger = 0
def go_for_a_walk(self): # <-- how would you test this function?
self.hunger += 1
```
### OPTIONAL Exercise FizzBuzz
#### What is FizzBuzz?

- fizz_buzz() takes an integer argument and returns it, BUT
- fails on zero or negative numbers
- instead returns "Fizz" on multiples of 3
- instead returns "Buzz" on multiples of 5
- instead returns "FizzBuzz" on multiples of 3 and 5
#### Test-Driven Development (TDD) Exercise
1. Create an empty function fizz_buzz()
1. Write the tests
1. Paste your tests in the collab document, and discuss
1. Now write a function code to make your tests pass
## Day 4 Introduction to Continuous Integration
### Exercise: Full-cycle collaborative workflow
This is an expanded version of the automated testing demonstration. The exercise is performed in a collaborative circle within the exercise group (breakout room).
The exercise takes 30-40 minutes.
In this exercise, everybody will:
A. Create a repository on GitHub/GitLab (everybody should use a different repository name for their repository)
B. Commit code to the repository and set up tests with GitHub Actions/ GitLab CI
C. Everybody will find a bug in their repository and open an issue in their repository
D. Then each one will clone the repo of one of their exercise partners, fix the bug, and open a pull request (GitHub)/ merge request (GitLab)
E. Everybody then merges their co-worker’s change

Overview of this exercise. Below we detail the steps.
#### Step 1: Create a new repository on GitHub/GitLab
- Select a different repository name than your colleagues (otherwise forking the same name will be strange)
- Before you create the repository, select “Initialize this repository with a README” (otherwise you try to clone an empty repo).
- Share the repository URL with your exercise group via shared document or chat
#### Step 2: Clone your own repository, add code, commit, and push
Clone the repository.
Add a file `example.py` containing:
```python=
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
assert add('space', 'ship') == 'spaceship'
def subtract(a, b):
return a + b # <--- fix this in step 8
# uncomment the following test in step 5
#def test_subtract():
# assert subtract(2, 3) == -1
```
Test `example.py` with `pytest`.
Then stage the file (`git add <filename>`), commit (`git commit -m "some commit message"`),
and push the changes (`git push origin main`).
#### Step 3: Enable automated testing
In this step we will enable GitHub Actions.
- Select "Actions" from your GitHub repository page. You get to a page "Get started with GitHub Actions".
- Select the button for "Set up this workflow" under Python Application.

Select “Python application” as the starter workflow.
:bulb: If it doesn't appear automatically search for "Python application"
GitHub creates the following file for you in the subfolder `.github/workflows`:
```yaml
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python application
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
```
Replace `pytest` by `pytest example.py`:
```yaml
- name: Test with pytest
run: |
pytest example.py
```

Commit the change by pressing the "Start Commit" button.
#### Step 4: Verify that tests have been automatically run
Observe in the repository how the test succeeds. While the test is executing, the repository has a yellow marker.
This is replaced with a green check mark, once the test succeeds.

Green check means passed.
Also browse the "Actions" tab and look at the steps there and their output.
#### Step 5: Add a test which reveals a problem
After you committed the workflow file, your GitHub/GitLab repository will be ahead of your local cloned repository. Update your local cloned repository:
```
$ git pull origin main
```
or
```
$ git pull origin master
```
Next uncomment the code in `example.py` under “step 5”, commit, and push. Verify that the test suite now fails on the “Actions” tab (GitHub) or the “CI/CD->Pipelines” tab (GitLab).
#### Step 6: Open and issue on GitHub/GitLab
Open a new issue in your repository about the broken test (click the “Issues” button on GitHub or GitLab and write a title for the issue). The plan is that your colleague will fix the issue through a pull/merge request
#### Step 7: Fork and clone the repository of your colleague
Fork the repository using the GitHub/GitLab web interface.
Make sure you clone the fork after you have forked it. Do not clone your colleague’s repository directly.
```
$ $ git clone https://gitlab.com/your-username/the-repository.git
```
#### Step 8: Fix the broken test
After you have fixed the code, commit the following commit message `"restore function subtract; fixes #1"` (assuming that you try to fix issue number 1).
Then push to your fork.
#### Step 9: Open a pull request (GitHub)/ merge request (GitLab)
Then before accepting the pull request/ merge request from your colleague, observe how GitHub Actions/ Gitlab CI automatically tested the code.
If you forgot to reference the issue number in the commit message, you can still add it to the pull request/ merge request: `my pull/merge request title, closes #NUMBEROFTHEISSUE`
#### Step 10
Observe how accepting the pull request/ merge request automatically closes the issue (provided the commit message or the pull request/ merge request contained the correct issue number).
Discuss whether this is a useful feature. And if it is, why do you think is it useful?