Skip to content

Commit 41b2dea

Browse files
Docs on unit testing (#489)
* Use consistent naming for unit tests * Add changelog entry * Separate testing contribution sections * Additional detail on testing page * Summary of existing unit tests * Use pFUnit @AssertTrue, @assertEqual, etc. more * Remove prefix from assert_isclose and assert_allclose * Drop unnecessary imports * Fix indentation * Comments for all unit tests * pFUnit primer * Maintain assert_<is/all>close with deprecation warnings --------- Co-authored-by: Jack Atkinson <109271713+jatkinson1000@users.noreply.github.com>
1 parent fc51573 commit 41b2dea

21 files changed

Lines changed: 529 additions & 423 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ For specific details see the [FTorch online documentation](https://cambridge-icc
1111

1212
### Added
1313

14+
- Add documentation on FTorch's unit testing approach. [#489](https://github.com/Cambridge-ICCS/FTorch/pull/489)
1415
- Batching in FTorch documented, including a new worked example, in
1516
[#500](https://github.com/Cambridge-ICCS/FTorch/pull/500).
1617
- Add subroutine for printing parameters associated with a Torch Model

examples/2_SimpleNet/simplenet_infer_fortran.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ program inference
99
torch_model_print_parameters
1010

1111
! Import our tools module for testing utils
12-
use ftorch_test_utils, only : assert_allclose
12+
use ftorch_test_utils, only : allclose
1313

1414
implicit none
1515

@@ -64,7 +64,7 @@ program inference
6464

6565
! Check output tensor matches expected value
6666
expected = [0.0_wp, 2.0_wp, 4.0_wp, 6.0_wp, 8.0_wp]
67-
test_pass = assert_allclose(out_data, expected, test_name="SimpleNet", rtol=1e-5)
67+
test_pass = allclose(out_data, expected, test_name="SimpleNet", rtol=1e-5)
6868

6969
! Cleanup
7070
call torch_delete(model)

examples/3_ResNet/resnet_infer_fortran.f90

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ program inference
77
torch_tensor_from_array, torch_model_load, torch_model_forward
88

99
! Import our tools module for testing utils
10-
use ftorch_test_utils, only : assert_isclose
10+
use ftorch_test_utils, only : isclose
1111

1212
implicit none
1313

@@ -99,8 +99,7 @@ subroutine main()
9999
probability = maxval(probabilities)
100100

101101
! Check top probability matches expected value
102-
test_pass = assert_isclose(probability, expected_prob, test_name="Check probability", &
103-
rtol=1e-5)
102+
test_pass = isclose(probability, expected_prob, test_name="Check probability", rtol=1e-5)
104103

105104
write (*,*) "Top result"
106105
write (*,*) ""

examples/5_MultiIO/multiionet_infer_fortran.f90

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ program inference
99
torch_tensor_from_array, torch_delete
1010

1111
! Import our tools module for testing utils
12-
use ftorch_test_utils, only : assert_allclose
12+
use ftorch_test_utils, only : allclose
1313

1414
implicit none
1515

@@ -62,9 +62,9 @@ program inference
6262

6363
! Check output tensors match expected values
6464
expected = [0.0_wp, 2.0_wp, 4.0_wp, 6.0_wp]
65-
test_pass = assert_allclose(out_data1, expected, test_name="MultiIO array1", rtol=1e-5)
65+
test_pass = allclose(out_data1, expected, test_name="MultiIO array1", rtol=1e-5)
6666
expected = [0.0_wp, -3.0_wp, -6.0_wp, -9.0_wp]
67-
test_pass = assert_allclose(out_data2, expected, test_name="MultiIO array2", rtol=1e-5)
67+
test_pass = allclose(out_data2, expected, test_name="MultiIO array2", rtol=1e-5)
6868

6969
! Cleanup
7070
call torch_delete(model)

examples/7_MultiGPU/multigpu_infer_fortran.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ program inference
1010
torch_delete
1111

1212
! Import our tools module for testing utils
13-
use ftorch_test_utils, only : assert_allclose
13+
use ftorch_test_utils, only : allclose
1414

1515
implicit none
1616

@@ -88,7 +88,7 @@ program inference
8888

8989
! Check output tensor matches expected value
9090
expected = [(2 * (device_index + i), i = 0, 4)]
91-
test_pass = assert_allclose(out_data, expected, test_name="MultiGPU")
91+
test_pass = allclose(out_data, expected, test_name="MultiGPU")
9292

9393
! Cleanup
9494
call torch_delete(model)

examples/8_MPI/mpi_infer_fortran.f90

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ program inference
88
torch_tensor_from_array, torch_model_load, torch_model_forward
99

1010
! Import our tools module for testing utils
11-
use ftorch_test_utils, only : assert_allclose
11+
use ftorch_test_utils, only : allclose
1212

1313
! Import MPI
1414
use mpi_f08, only : mpi_comm_rank, mpi_comm_size, mpi_comm_world, mpi_finalize, mpi_float, &
@@ -94,7 +94,7 @@ program inference
9494
do rank_chk = 0, size-1
9595
expected = [(2 * (rank_chk + i), i = 0, 4)]
9696
result_chk(:) = recvbuf(:,rank_chk+1)
97-
test_pass = assert_allclose(result_chk, expected, test_name="MPI")
97+
test_pass = allclose(result_chk, expected, test_name="MPI")
9898
if (.not. test_pass) then
9999
write(unit=stdout, fmt="('rank ',i1,' result: ')") rank_chk
100100
write(unit=stdout, fmt=100) result_chk(:)

examples/9_Autograd/autograd.f90

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ program autograd
99
torch_tensor_from_array, torch_tensor_get_gradient
1010

1111
! Import our tools module for testing utils
12-
use ftorch_test_utils, only : assert_allclose
12+
use ftorch_test_utils, only : allclose
1313

1414
implicit none
1515

@@ -45,7 +45,7 @@ program autograd
4545

4646
! Check output tensor matches expected value
4747
expected(:) = [-12.0_wp, 65.0_wp]
48-
if (.not. assert_allclose(out_data1, expected, test_name="autograd_Q")) then
48+
if (.not. allclose(out_data1, expected, test_name="autograd_Q")) then
4949
write(*,*) "Error :: value of Q does not match expected value"
5050
stop 999
5151
end if
@@ -64,13 +64,13 @@ program autograd
6464
! Check the gradients take expected values
6565
write(*,*) "dQda = 9*a^2 = ", out_data2
6666
expected(:) = [36.0_wp, 81.0_wp]
67-
if (.not. assert_allclose(out_data2, expected, test_name="autograd_dQdb")) then
67+
if (.not. allclose(out_data2, expected, test_name="autograd_dQdb")) then
6868
write(*,*) "Error :: value of dQdb does not match expected value"
6969
stop 999
7070
end if
7171
write(*,*) "dQdb = - 2*b = ", out_data3
7272
expected(:) = [-12.0_wp, -8.0_wp]
73-
if (.not. assert_allclose(out_data3, expected, test_name="autograd_dQdb")) then
73+
if (.not. allclose(out_data3, expected, test_name="autograd_dQdb")) then
7474
write(*,*) "Error :: value of dQdb does not match expected value"
7575
stop 999
7676
end if

pages/developer/testing.md

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
title: FTorch test suite
22
author: Joe Wallwork
3-
date: Last Updated: October 2025
3+
date: Last Updated: January 2026
44

55

66
## Testing
@@ -12,7 +12,9 @@ tests based on a subset of the [worked examples](|page|/usage/worked_examples.ht
1212
- [Running Tests](#running)
1313
- [Unit Tests](#unit-tests)
1414
- [Integration Tests](#integration-tests)
15-
- [Adding Tests](#adding-tests)
15+
- [Contributing Tests](#contributing-tests)
16+
- [Contributing Unit Tests](#contributing-unit-tests)
17+
- [Contributing Integration Tests](#contributing-integration-tests)
1618

1719

1820
### Building
@@ -60,7 +62,7 @@ demonstrates 'good' versus 'bad' practice, as opposed to functionality.
6062

6163
Ensure that the Python virtual environment used when
6264
building is active and then run `ctest` from the build directory to execute all tests.
63-
Use [ctest arguments](https://cmake.org/cmake/help/latest/manual/ctest.1.html)
65+
Use [CTest arguments](https://cmake.org/cmake/help/latest/manual/ctest.1.html)
6466
for greater control over the testing configuration.
6567

6668
This will produce a report on which of the requested tests passed, and which, if any,
@@ -70,39 +72,78 @@ failed for your build.
7072

7173
Unit tests may be executed in the following ways:
7274

73-
1. To run just the unit tests (tests whose names start with 'unittest') use
74-
`ctest -R unittest`.
75-
2. To run a specific unit test use
76-
`ctest -R unittest_tensor_constructors_destructors`, for example.
75+
1. To run all unit tests, use `ctest -R unittest`.<br>
76+
(`ctest -R` accepts a regular expression to search for and this works because
77+
all unit tests start with `unittest`.)
78+
2. Use a different regular expression to select a subset of unit tests, e.g.,
79+
`ctest -R unittest_tensor` to run all unit tests related to `torch_tensor`s.
80+
3. To run a specific unit test just use its full name, e.g.
81+
`ctest -R unittest_tensor_constructors_destructors`.
82+
83+
For a summary of the current unit tests, see the
84+
[README](https://github.com/Cambridge-ICCS/FTorch/blob/main/test/README.md).
7785

7886
#### Integration tests
7987

8088
Integration tests may be executed in the following ways:
8189

82-
1. To run just the integration tests (tests whose names start with 'example') use
83-
`ctest -R example`
90+
1. To run just the integration tests, use `ctest -R example`.<br>
91+
(`ctest -R` accepts a regular expression to search for and this works because
92+
all integration tests start with `example`.)
8493
2. To run a specific integration test use `ctest -R example2`, for example.<br>
8594
Alternatively navigate to the corresponding example in `${BUILD_DIR}/examples`
8695
and call `ctest`.
8796

8897

89-
### Adding Tests
90-
91-
New components should come with unit tests written using the pFUnit framework.
92-
93-
- New unit tests should be added to the `test/unit/` directory and start with
94-
`unittest_`.
95-
- New tests need including in the `CMakeLists.txt` in that directory in order
96-
to be built as part of the test suite.
98+
### Contributing tests
99+
100+
#### Contributing unit tests
101+
102+
New components should come with unit tests written using the
103+
[pFUnit](https://github.com/Goddard-Fortran-Ecosystem/pFUnit) framework.
104+
105+
- New unit tests should be added to the `test/unit/` directory, have names
106+
that start with `unittest_` and use the `.pf` extension.
107+
- Unit test files should include a `module` with the same name as the file
108+
(minus the extension).
109+
- If MPI parallelism is required for the unit test, use the `pfunit` module
110+
that comes with pFUnit, otherwise its `funit` module should be sufficient.
111+
- Tests contained within the module should be written as subroutines with the
112+
`@test` decorator and name starting with `test_`.
113+
- pFUnit's `@assertTrue`, `@assertFalse`, and `@assertEqual` decorators should be
114+
used to check expected outcomes.
115+
- In some cases, it can be useful to use parameterisation. This can be
116+
achieved by defining a derived type of `AbstractTestParameter` and giving it
117+
the `@testParameter` decorator. Additionally, define a derived type of
118+
`ParameterizedTestCase` and give it the `@testCase` decorator. The test case
119+
acts as a constructor that determines how to pass the parameters to the
120+
tests. Where parameterisation is used, parameter sets should be defined as
121+
functons to create instances of the derived test parameter type.
122+
Parameterised tests should pass the parameters through the `@test`
123+
decorator. For example,
124+
```fortran
125+
@test(testparameters={get_parameters()})
126+
```
127+
where `get_parameters` is such a function as mentioned above. See existing
128+
unit tests for examples of this.
129+
- New unit tests should be included in `test/unit/CMakeLists.txt` in order to
130+
be built as part of the test suite.
131+
- Don't forget to add a brief summary of the new unit test in the unit test
132+
[README](https://github.com/Cambridge-ICCS/FTorch/blob/main/test/README.md).
133+
134+
#### Contributing integration tests
97135
98136
New functionalities should come with integration tests in the form of worked
99137
examples.
100138
101139
- These should take the form of a new example in the `examples/` directory.
140+
- Create a subdirectory named with the next sequential number and a descriptive
141+
name, e.g. `9_NewFeature`.
102142
- In addition to a `CMakeLists.txt` to build the example code there
103143
should also be a section at the end setting up running of the example
104-
using CTest.
105-
- Integration test names should start with `example_`
106-
- New examples will also need including in `examples/CMakeLists.txt`
107-
- Ensure the documentation on [worked examples](|page|/usage/worked_examples.html) is
108-
updated accordingly.
144+
using [CTest](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Testing%20With%20CMake%20and%20CTest.html).
145+
- Integration test names should start with `example` followed by the example
146+
number, e.g. `example9`.
147+
- New examples will also need including in `examples/CMakeLists.txt`
148+
- Ensure the documentation on
149+
[worked examples](|page|/usage/worked_examples.html) is updated accordingly.

0 commit comments

Comments
 (0)