Skip to content

Commit f5a8c46

Browse files
committed
source commit: e716a59
0 parents  commit f5a8c46

14 files changed

Lines changed: 1377 additions & 0 deletions

1-intro-to-unit-tests.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
title: "Introduction to Unit Testing"
3+
teaching:
4+
exercises:
5+
---
6+
7+
:::::::::::::::::::::::::::::::::::::: questions
8+
9+
- What is unit testing?
10+
- Why do we need unit tests?
11+
- What makes src code difficult to unit test?
12+
13+
::::::::::::::::::::::::::::::::::::::::::::::::
14+
15+
::::::::::::::::::::::::::::::::::::: objectives
16+
17+
- Define the key aspects of a good unit test (isolated, testing minimal functionality, fast, etc).
18+
- Understand the key anatomy of a unit test in any language.
19+
- Explain the benefit of unit tests on top of integration/e2e tests.
20+
- Understand when to run unit tests.
21+
22+
::::::::::::::::::::::::::::::::::::::::::::::::
23+
24+
## What is unit testing?
25+
26+
Unit testing is a way of verifying the validity of a code base by testing its smallest individual components, or **units**.
27+
28+
>*"If the parts don't work by themselves, they probably won't work well together"*
29+
> -- (Thomas and Hunt, 2019, [The pragmatic programmer](https://search.worldcat.org/search?q=bn:9780135957059), Topic 51).
30+
31+
Several key aspects define a unit test. They should be...
32+
33+
- **Isolated** - Does not rely on any other unit of code within the repository.
34+
- **Minimal** - Tests only one unit of code.
35+
- **Fast** - Run on the scale of ms or s.
36+
37+
::::::::::::::::::::::::::::::::::::: callout
38+
39+
#### Other forms of testing
40+
41+
There are other forms of testing, such as integration testing in which two or more units of a code base are tested to verify that they work together, or that they are correctly **integrated**.
42+
However, today we are focusing on unit tests as it is often the case that many of these larger tests are written using the same test tools and frameworks, hence we will make progress with both by starting with unit testing.
43+
44+
::::::::::::::::::::::::::::::::::::::::::::::::
45+
46+
### What does a unit test look like?
47+
48+
All unit tests tend to follow the same pattern of Given-When-Then.
49+
50+
- **Given** we are in some specific starting state
51+
- Units of code almost always have some inputs. These inputs may be scalars to be passed into a function, but they may also be an external dependency such as a database, file or array which must be allocated.
52+
- This database, file or array memory must exist before the unit can be tested. Hence, we must set up this state in advance of calling the unit we are testing.
53+
- **When** we carry out a specific action
54+
- This is the step in which we call the unit of code to be tested, such as a call to a function or subroutine.
55+
- We should limit the number of actions being performed here to ensure it is easy to determine which unit is failing in the event that a test fails.
56+
- **Then** some specific event/outcome will have occurred.
57+
- Once we have called our unit of code, we must check that what we expected to happen did indeed happen.
58+
- This could mean comparing a scalar or vector quantity returned from the called unit against some expected value. However, it could be something more complex such as validating the contents of a database or outputted file.
59+
60+
::::::::::::::::::::::::::::::::::::: challenge
61+
62+
#### Challenge 1: Write a unit test in sudo code.
63+
64+
Assuming you have a function `reverse_array` which reverses the order of an allocated array. Write a unit test in pseudo code for `reverse_array` using the pattern above.
65+
66+
:::::::::::::::::::::::: solution
67+
68+
```
69+
! Given
70+
Allocate the input array `input_array`
71+
Fill `input_array`, for example with `(1,2,3,4)`
72+
Allocate the expected output array `expected_output_array`
73+
Fill `expected_output_array` with the correct expected output, i.e., `(4,3,2,1)`
74+
75+
! When
76+
Call `reverse_array` with `input_array`
77+
78+
! Then
79+
for each element in `input_array`:
80+
Assert that the corresponding element of `expected_output_array` matches that of `input_array`
81+
```
82+
83+
:::::::::::::::::::::::::::::::::
84+
::::::::::::::::::::::::::::::::::::::::::
85+
86+
### When should unit tests be run?
87+
88+
A major benefit of unit tests is the ability to identify bugs at the earliest possible stage. Therefore, unit tests should be run frequently throughout the development process. Passing unit tests give you and your collaborators confidence that changes to your code aren't modifying the previously expected behaviour, so run your unit tests...
89+
90+
- if you make a change locally
91+
- if you raise a merge request
92+
- if you plan to do a release
93+
- if you are reviewing someone else's changes
94+
- if you have recently installed your code into a new environment
95+
- if your dependencies have been updated
96+
97+
Basically, all the time.
98+
99+
### Do we really need unit tests?
100+
101+
Yes!
102+
103+
You may be thinking that you don't require unit tests as you already have some well-defined end-to-end test cases which demonstrate that your code base works as expected. However, consider the case where this end-to-end test begins to fail. The message for this failure is likely to be something along the lines of
104+
105+
```
106+
Expected my_special_number to be 1.234 but got 5.678
107+
```
108+
109+
If you have a comprehensive understanding of your code, perhaps this is all you need. However, assuming the newest feature that caused this failure was not written by you, it's going to be difficult to identify what is going wrong without some lengthy debugging.
110+
111+
Now imagine the situation where this developer added unit tests for their new code. When running these unit tests, you may see something like
112+
113+
```
114+
test_populate_arrays Failed: Expected 1 for index 1 but got 0
115+
```
116+
117+
This is much clearer. We immediately have an idea of what could be going wrong and the unit test itself will help us determine the problematic code to investigate.
118+
119+
::::::::::::::::::::::::::::::::::::: challenge
120+
121+
### Challenge 2: Unit test bad practices
122+
123+
Take a look at [1-into-to-unit-tests/challenge](https://github.com/UCL-ARC/fortran-unit-testing-exercises/tree/main/episodes/1-into-to-unit-tests/challenge) in the exercises repository.
124+
125+
:::::::::::::::::::::::::::::::: solution
126+
127+
A solution is provided in [1-into-to-unit-tests/solution](https://github.com/UCL-ARC/fortran-unit-testing-exercises/tree/main/episodes/1-into-to-unit-tests/solution).
128+
129+
:::::::::::::::::::::::::::::::::::::::::
130+
::::::::::::::::::::::::::::::::::::::::::::::::
131+
132+
## References
133+
134+
- David Thomas and Andrew Hunt (2019). [The Pragmatic Programmer: your journey to mastery](https://search.worldcat.org/search?q=bn:9780135957059), 20th Anniversary Edition, 2nd Edition. Addison-Wesley Professional.

2-intro-to-fortran-unit-tests.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
title: "Introduction to Unit Testing in Fortran"
3+
teaching:
4+
exercises:
5+
---
6+
7+
:::::::::::::::::::::::::::::::::::::: questions
8+
9+
- Why is there a lack of unit tests in existing Fortran codes?
10+
- Why do we need to write unit tests for Fortran code?
11+
12+
::::::::::::::::::::::::::::::::::::::::::::::::
13+
14+
::::::::::::::::::::::::::::::::::::: objectives
15+
16+
- Understand the need for unit testing Fortran code.
17+
- Identify Fortran code which is problematic for unit testing.
18+
- Understand steps to make Fortran more unit testable.
19+
20+
::::::::::::::::::::::::::::::::::::::::::::::::
21+
22+
## Why your Fortran may not already be unit tested
23+
24+
Many Fortran codes don't yet have unit tests. This can be for a number of reasons...
25+
26+
- Lack of resources (time and/or money).
27+
- Funding is earmarked for furthering science.
28+
- The code is not able be unit tested in its current state.
29+
- Code is written in Fortran 77 or uses outdated paradigms which make unit testing near impossible.
30+
- Lack of skills.
31+
- Developers don't have the skills to implement unit tests.
32+
33+
## What's the matter with Fortran?
34+
35+
Assuming we have the time, money and the skills to implement some unit tests in a Fortran project, what about the code makes it a challenge to do so?
36+
37+
Various bad practices can make it difficult to unit test Fortran including...
38+
39+
- Global variables
40+
- Large, multipurpose procedures
41+
42+
::::::::::::::::::::::::::::::::::::: challenge
43+
44+
#### Challenge 1: Identify bad practice for unit testing Fortran
45+
46+
Take a look at [2-intro-to-fortran-unit-tests/challenge](https://github.com/UCL-ARC/fortran-unit-testing-exercises/tree/main/episodes/2-intro-to-fortran-unit-tests/challenge) in the exercises repository.
47+
48+
:::::::::::::::::::::::::::::::: solution
49+
50+
A solution is provided in [2-intro-to-fortran-unit-tests/solution](https://github.com/UCL-ARC/fortran-unit-testing-exercises/tree/main/episodes/2-intro-to-fortran-unit-tests/solution).
51+
52+
:::::::::::::::::::::::::::::::::::::::::
53+
::::::::::::::::::::::::::::::::::::::::::
54+
55+
## How to start improving
56+
57+
Unit tests do not need to be added to a Fortran project in one huge implementation. The best approach is to incrementally add unit tests
58+
59+
1. Ensure an end-to-end test exists
60+
- This can be written in a different language if needed. Pytest is often a good choice.
61+
2. Identify a procedure with minimal dependencies
62+
- If one can't be found extract one into a procedure
63+
3. Ensure end-to-end test covers this procedure
64+
- Try to break the procedure and check your test fails as expected.
65+
4. Remove all non-local state in the procedure
66+
5. Check end-to-end test passes
67+
6. Write unit test(s) for procedure
68+
7. Repeat steps 1-6
69+
70+
This incremental approach will, over time, become smoother. As more unit tests exist the code will become easier to unit test as its infrastructure will become more mature and feature rich.
71+
72+
::::::::::::::::::::::: callout
73+
74+
### Working effectively with legacy code
75+
76+
Untested codebase is effectively legacy code. Therefore, a great resource for us is *[Working Effectively with Legacy Code](https://search.worldcat.org/title/660166658)* (Feathers, 2004)
77+
78+
If you don't have time to read the entire book, there is a good summary of the key point in this blog post [The key points of Working Effectively with Legacy Code](https://understandlegacycode.com/blog/key-points-of-working-effectively-with-legacy-code/)
79+
80+
:::::::::::::::::::::::::::::::
81+
82+
## References
83+
84+
- Michael Feathers (2004). [Working Effectively with Legacy Code](https://search.worldcat.org/title/660166658). Pearson.

0 commit comments

Comments
 (0)