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.

BASH

$ fpm --version
Version:     0.12.0, alpha

$ cmake --version
cmake version 3.27.0

$ ./build-pfunit.sh -t
TODO: Add output from tetsing pfunit and implement testing pFUnit.

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.

References