Content from Introduction
Last updated on 2025-07-03 | Edit this page
Overview
Questions
- How can you follow along with this walkthrough?
Objectives
- Can clone the exercises repo for the walkthrough.
- Understand the objectives of this walkthrough.
Introduction
This walkthrough aims to…
- Demonstrate the importance of testing Fortran codes with unit tests.
- Show how to write unit tests for Fortran Code using three different frameworks: test-drive, veggies and pFUnit.
- Show how to integrate these tests with both CMake and FPM build systems.
- Highlight the differences between writing unit tests for parallel and serial Fortran code.
Putting it into practice
Throughout this walkthrough, we will use the repository UCL-ARC/fortran-unit-testing-exercises which contains example exercises written in Fortran.
Challenge 1: Can you clone the exercises repository
Try to clone the exercises we will use throughout this lesson.
BASH
$ git clone https://github.com/UCL-ARC/fortran-unit-testing-exercises.git
Cloning into 'fortran-unit-testing-exercises'...
remote: Enumerating objects: 256, done.
remote: Counting objects: 100% (256/256), done.
remote: Compressing objects: 100% (140/140), done.
remote: Total 256 (delta 98), reused 229 (delta 71), pack-reused 0 (from 0)
Receiving objects: 100% (256/256), 45.96 KiB | 4.18 MiB/s, done.
Resolving deltas: 100% (98/98), done.
Dependencies
To following along with this lesson’s exercises you will require the following
- Fortran Package Manager (FPM)
- CMake
- pFUnit
Challenge 2: Can you install the above dependencies?
Try to install the dependencies listed above.
- FPM Install instructions
- CMake can be installed via homebrew on mac or your package manager (apt, etc) on Linux.
- pFUnit can be install via the bash script provided in the exercises repo build-pfunit.sh.
Content from Introduction to Unit Testing
Last updated on 2025-07-07 | Edit this page
Overview
Questions
- What is unit testing?
- Why do we need unit tests?
- What makes src code difficult to unit test?
Objectives
- Define the key aspects of a good unit test (isolated, testing minimal functionality, fast, etc).
- Understand the key anatomy of a unit test in any language.
- Explain the benefit of unit tests on top of integration/e2e tests.
- Understand when to run unit tests.
What is unit testing?
Unit testing is a way of verifying the validity of a code base by testing its smallest individual components, or units.
“If the parts don’t work by themselves, they probably won’t work well together” – (Thomas and Hunt, 2019, The pragmatic programmer, Topic 51).
Several key aspects define a unit test. They should be…
- Isolated - Does not rely on any other unit of code within the repository.
- Minimal - Tests only one unit of code.
- Fast - Run on the scale of ms or s.
Other forms of testing
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. 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.
What does a unit test look like?
All unit tests tend to follow the same pattern of Given-When-Then.
-
Given we are in some specific starting state
- 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.
- 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.
-
When we carry out a specific action
- This is the step in which we call the unit of code to be tested, such as a call to a function or subroutine.
- 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.
-
Then some specific event/outcome will have
occurred.
- Once we have called our unit of code, we must check that what we expected to happen did indeed happen.
- 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.
Challenge 1: Write a unit test in sudo code.
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.
! Given
Allocate the input array `input_array`
Fill `input_array`, for example with `(1,2,3,4)`
Allocate the expected output array `expected_output_array`
Fill `expected_output_array` with the correct expected output, i.e., `(4,3,2,1)`
! When
Call `reverse_array` with `input_array`
! Then
for each element in `input_array`:
Assert that the corresponding element of `expected_output_array` matches that of `input_array`
When should unit tests be run?
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…
- if you make a change locally
- if you raise a merge request
- if you plan to do a release
- if you are reviewing someone else’s changes
- if you have recently installed your code into a new environment
- if your dependencies have been updated
Basically, all the time.
Do we really need unit tests?
Yes!
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
Expected my_special_number to be 1.234 but got 5.678
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.
Now imagine the situation where this developer added unit tests for their new code. When running these unit tests, you may see something like
test_populate_arrays Failed: Expected 1 for index 1 but got 0
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.
Challenge 2: Unit test bad practices
Take a look at 1-into-to-unit-tests/challenge-2 in the exercises repository.
A solution is provided in episodes/1-into-to-unit-tests/challenge-2/test/solution.
References
- David Thomas and Andrew Hunt (2019). The Pragmatic Programmer: your journey to mastery, 20th Anniversary Edition, 2nd Edition. Addison-Wesley Professional.