|
| 1 | +--- |
| 2 | +title: "pFUnit basics" |
| 3 | +teaching: |
| 4 | +exercises: |
| 5 | +--- |
| 6 | + |
| 7 | +:::::::::::::::::::::::::::::::::::::: questions |
| 8 | + |
| 9 | +- What is the syntax of writing a unit test in Fortran? |
| 10 | +- How do I build my tests with my existing build system? |
| 11 | + |
| 12 | +:::::::::::::::::::::::::::::::::::::::::::::::: |
| 13 | + |
| 14 | +::::::::::::::::::::::::::::::::::::: objectives |
| 15 | + |
| 16 | +- Able to write a unit test for a Fortran procedure with test-drive, veggies and/or pFUnit. |
| 17 | +- Understand the similarities between each framework and where they differ. |
| 18 | + |
| 19 | +:::::::::::::::::::::::::::::::::::::::::::::::: |
| 20 | + |
| 21 | +## What framework will we look at? |
| 22 | + |
| 23 | +There are multiple frameworks available for writing unit tests in Fortran, as detailed on the |
| 24 | +[Fortran Lang website](https://fortran-lang.org/packages/programming/). However, we recommend |
| 25 | +the use of [pFUnit](https://github.com/Goddard-Fortran-Ecosystem/pFUnit) as it is… |
| 26 | + |
| 27 | +- the most feature rich framework. |
| 28 | +- the most widely used framework. |
| 29 | +- being maintained. |
| 30 | +- able to integrate with CMake and make. |
| 31 | + |
| 32 | +**Key features of pFUnit:** |
| 33 | + |
| 34 | +- **Supports MPI**: Supports testing MPI parallelized code, including parametrizing tests by |
| 35 | + number of MPI ranks. |
| 36 | +- **Simple interface**: Tests are written in **.pf** format which is then pre-processed by a tool |
| 37 | + provided by pFUnit into **.f90** before compilation. This removes the need to write a lot of |
| 38 | + boilerplate code. |
| 39 | + |
| 40 | +## The most basic pFUnit test |
| 41 | + |
| 42 | +As we've seen in the [previous episode](../3-writing-your-first-unit-test.html), if we were to write our own unit tests using a |
| 43 | +custom testing setup we would need to define a test runner that could track success and failure states for each test and report |
| 44 | +the reason for each failure back to us. |
| 45 | + |
| 46 | +Alternatively, if we were to use pFUnit, there is no longer a need to define this test runner because pFUnit handles that for us. |
| 47 | +Therefore, the most basic test we can define using pFunit becomes simple. For example, if we wanted to test the Fortran intrinsic |
| 48 | +function **dot_product**, we could write the following test. |
| 49 | + |
| 50 | +```fortran |
| 51 | +module test_dot_product_intrinsic |
| 52 | + use funit |
| 53 | + implicit none |
| 54 | +contains |
| 55 | + @Test |
| 56 | + subroutine test_dot_product() |
| 57 | + integer :: a(10), b(10), c |
| 58 | +
|
| 59 | + ! Define inputs and expected outputs for the scenario we want to test |
| 60 | + a = [1,2,3,4,5,6,7,8,9,10] |
| 61 | + b = [11,12,13,14,15,16,17,18,19,20] |
| 62 | + c = 935 |
| 63 | +
|
| 64 | + ! Check that the call to dot_product returned what we expect |
| 65 | + @assertEqual(c, dot_product(a, b), message="Unexpected value returned for the dot_product") |
| 66 | +
|
| 67 | + end subroutine test_dot_product |
| 68 | +end module test_dot_product_intrinsic |
| 69 | +``` |
| 70 | + |
| 71 | +Here we have introduced some new syntax in the form of **@Test** and **@AssertEqual**. These are pFUnit pre-processor directives |
| 72 | +which simplify how we write tests: |
| 73 | + |
| 74 | +- **@Test** designates the subroutine **test_dot_product** as a test that should be ran on execution of your pFUnit test suite. |
| 75 | +- **@AssertEqual** is one of many assert directives provided by pFUnit. More specifically, **@AssertEqual** allows the |
| 76 | + exact comparison of values (also works for comparing arrays). For a full list of the available assertion directives see |
| 77 | + [pFUnit documentation page for their preprocessor directives](https://pfunit.sourceforge.net/page_Assert.html) |
| 78 | + - As is done here, it is recommended to provide a helpful message, in case of an assertion |
| 79 | + failing, to help diagnose the issue. |
| 80 | + |
| 81 | +::: callout |
| 82 | + |
| 83 | +### @AssertEqual for floating point values |
| 84 | + |
| 85 | +For floating point values, @AssertEqual no longer carries out an exact comparison but become a comparison up to a tolerance. |
| 86 | + |
| 87 | +::: |
| 88 | + |
| 89 | +If we then wish to add a new test case we can add another subroutine, again decorated with **@Test**: |
| 90 | + |
| 91 | +```fortran |
| 92 | +module test_dot_product_intrinsic |
| 93 | + use funit |
| 94 | + implicit none |
| 95 | +contains |
| 96 | + @Test |
| 97 | + subroutine test_dot_product() |
| 98 | + integer :: a(10), b(10), c |
| 99 | +
|
| 100 | + ! Define inputs and expected outputs for the scenario we want to test |
| 101 | + a = [1,2,3,4,5,6,7,8,9,10] |
| 102 | + b = [11,12,13,14,15,16,17,18,19,20] |
| 103 | + c = 935 |
| 104 | +
|
| 105 | + ! Check that the call to dot_product returned what we expect |
| 106 | + @AssertEqual(c, dot_product(a, b), message="Unexpected value returned for the dot_product") |
| 107 | +
|
| 108 | + end subroutine test_dot_product |
| 109 | +
|
| 110 | + @Test |
| 111 | + subroutine test_dot_product_all_zeros() |
| 112 | + integer :: a(10), b(10), c |
| 113 | +
|
| 114 | + ! Define inputs and expected outputs for the scenario we want to test |
| 115 | + a = 0 |
| 116 | + b = 0 |
| 117 | + c = 0 |
| 118 | +
|
| 119 | + ! Check that the call to dot_product returned what we expect |
| 120 | + @AssertEqual(c, dot_product(a, b), message="Unexpected value returned for the dot_product") |
| 121 | +
|
| 122 | + end subroutine test_dot_product_all_zeros |
| 123 | +end module test_dot_product_intrinsic |
| 124 | +``` |
| 125 | + |
| 126 | +::: challenge |
| 127 | + |
| 128 | +### Challenge: Test temperature conversions using pFUnit |
| 129 | + |
| 130 | +Continuing with part two of |
| 131 | +[3-writing-your-first-unit-test/challenge](https://github.com/carpentries-incubator/fortran-unit-testing/tree/main/exercises/3-writing-your-first-unit-test/challenge) |
| 132 | +from the exercises. Write a single test for the temperature conversion using pFUnit. |
| 133 | + |
| 134 | +::: solution |
| 135 | + |
| 136 | +A solution is provided in [3-writing-your-first-unit-test/solution](https://github.com/carpentries-incubator/fortran-unit-testing/tree/main/exercises/3-writing-your-first-unit-test/solution). |
| 137 | + |
| 138 | +::: |
| 139 | + |
| 140 | +::: |
0 commit comments