|
| 1 | +--- |
| 2 | +title: Writing Unit Tests |
| 3 | +permalink: /docs/Writing-Unit-Tests/ |
| 4 | +--- |
| 5 | + |
| 6 | +This document covers the question "How do I write unit tests for SU2?" |
| 7 | +Before reading this page, make sure you are familiar with the pages |
| 8 | +[Running Unit Tests](/docs_v7/Running-Unit-Tests/) and |
| 9 | +[Build SU2 on Linux/MacOS](/docs_v7/Build-SU2-Linux-MacOS/). |
| 10 | + |
| 11 | +This document is intended to be an introduction only. There are plenty of |
| 12 | +details that are *not* covered in this document. This omission is |
| 13 | +intentional. The Catch2 documentation is the best place to learn about |
| 14 | +using Catch2, so this guide does not duplicate any discussion of the |
| 15 | +complex features of Catch2. These more complex features include: |
| 16 | + |
| 17 | ++ Grouping tests into sections with similar setup or teardown |
| 18 | ++ Parameterized tests |
| 19 | ++ Logging context to report alongside failures |
| 20 | ++ Tests that are expected to throw exceptions |
| 21 | ++ Hiding tests from the default list |
| 22 | ++ Using Catch2 with a debugger |
| 23 | ++ Custom matchers |
| 24 | + |
| 25 | +Please refer to the Catch2 documentation for more information on these |
| 26 | +capabilities. |
| 27 | + |
| 28 | +## Catch2 Unit-Testing Framework |
| 29 | + |
| 30 | +A unit-testing framework takes most of the boilerplate code out of unit |
| 31 | +testing. There's no need to manually create a `main()` for each test, |
| 32 | +nor do you need to write your own floating-point matcher. A good unit-testing |
| 33 | +framework also provides many command-line tools, such as filtering the tests |
| 34 | +and controlling the output. |
| 35 | +Catch2 was chosen for the unit testing library for the following reasons: |
| 36 | + |
| 37 | ++ It can be included as a header-only library. |
| 38 | ++ It has a very clean, easy-to-use syntax |
| 39 | ++ It has widespread use, including the FEniCS library. |
| 40 | + |
| 41 | +## Design Overview |
| 42 | + |
| 43 | ++ Catch2 is included as a header file in the `externals` folder, and is |
| 44 | + distributed with the SU2 source code. |
| 45 | ++ All tests are placed in a top-level directory named `UnitTests`. Inside this directory is a structure just like the existing folder structure (e.g. `SU2_CFD/numerics`), except that there are not separate `src` and `include` folders. |
| 46 | ++ Tests are grouped by class, and put in files such as `CNumerics_tests.cpp`. |
| 47 | ++ A single test executable is compiled and run, as opposed to a separate test executable for each group of tests. With the single test executable, you can always filter down to groups of tests or individual tests. |
| 48 | ++ When `ninja test` or `meson test` is run, the test executable is only run once, sweeping through all the unit tests. The result only shows a single failure or a single success. If more detail is desired, then the test executable can be run manually. |
| 49 | ++ The relevant SU2 code is compiled as a library (e.g. `libSU2core`) and then linked into both the main executable (`SU2_CFD`) and the test program (`test_driver`). |
| 50 | + |
| 51 | +## Writing a Basic Unit Test |
| 52 | + |
| 53 | +There are many working examples of unit tests within the SU2 codebase. |
| 54 | +To see them, browse the folder `UnitTests/`. For an introduction, a simple |
| 55 | +example is given here. In order to add this test, the developer would create |
| 56 | +a `.cpp` file with something like the following lines: |
| 57 | + |
| 58 | +``` |
| 59 | +#include "catch.hpp" |
| 60 | +
|
| 61 | +TEST_CASE("Addition", "[arithmetic]") { |
| 62 | +
|
| 63 | + int a = 2, b = 2; |
| 64 | + |
| 65 | + REQUIRE(a + b == 4); |
| 66 | +
|
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | ++ The header `catch.hpp` contains the macros used for unit tests. These |
| 71 | + macros replace a lot of the boilerplate code needed for testing. |
| 72 | ++ `TEST_CASE` is used to define a test case. Each unit test *must* start with |
| 73 | + this macro. |
| 74 | ++ Here, `Addition` is the name of the test case. The name is a proper, |
| 75 | + string, and can have spaces, numbers, or puncutation. This stands in |
| 76 | + contrast to many unit-testing frameworks, such as Boost test or Google |
| 77 | + test, where the test names must be valid C++ variable names. |
| 78 | ++ The second argument, `[arithmetic]` is a tag. Tags are used to group |
| 79 | + related tests together, even if they occur in different test cases or |
| 80 | + files. |
| 81 | ++ `REQUIRE()` is similar to `assert()`. It checks that the contained logical |
| 82 | + statement is true. If not, it will display relevant error messages. |
| 83 | ++ You can also use the macro `CHECK()`. This also checks if the contained |
| 84 | + logical statment is true, but it does not stop on the first failure. It |
| 85 | + will record the results and continue execution, whether or not the check |
| 86 | + is true. |
| 87 | + |
| 88 | +For more detail on writing tests, please visit Catch2's documentation. |
| 89 | + |
| 90 | +## Adding the Unit Test to the Test Driver |
| 91 | + |
| 92 | +In order to run the unit test, it must be added to the meson build scripts. |
| 93 | +Add the new `.cpp` file to `UnitTests/meson.build`. This will tell meson |
| 94 | +to build your unit test, then run it using the test driver provided by Catch2. |
| 95 | + |
| 96 | +## Floating Point Unit Tests |
| 97 | + |
| 98 | +An important part of unit testing in scientific computing is floating |
| 99 | +point comparisons. For tests with floating point numbers, it is not proper |
| 100 | +to require a strong equality. Instead, Catch2 provides the wrapper |
| 101 | +class `Approx`. `Approx` can be placed on either side of an equality |
| 102 | +to indicate that two floating-point numbers are only *approximately* equal. |
| 103 | + |
| 104 | +For example: |
| 105 | +``` |
| 106 | +REQUIRE(volume == Approx(0.85478577)); |
| 107 | +``` |
| 108 | + |
| 109 | +The type of approximation and the precision can be customized. For more |
| 110 | +detail on these customizations, please see Catch2's documentation. |
| 111 | +Alternatively, Catch2 also supplies custom matchers for use with |
| 112 | +floating-point numbers. These include `WithinAbs`, `WithinULP`, |
| 113 | +and `WithinRel`. |
| 114 | + |
| 115 | +## Directdiff and AD Tests |
| 116 | + |
| 117 | +You can also write tests that involve algorithmic differentiation (AD) |
| 118 | +and direct differentiation (DD). There are a few things that must be done |
| 119 | +differently when writing these tests: |
| 120 | + |
| 121 | +- The test must be linked with the correct AD or DD libraries. To do |
| 122 | + so, add the test to the meson lists `su2_cfd_tests_ad` or |
| 123 | + `su2_cfd_tests_dd`, respectively, in `UnitTests/meson.build`. |
| 124 | +- When using the `su2double` datatype in and AD or DD build, the value |
| 125 | + stored in su2double is accessed using `SU2_TYPE::GetValue()`. A simple |
| 126 | + assert such as `CHECK(y == Approx(64.0)` will fail to compile |
| 127 | + if `y` is an su2double. This is because you're trying to compare an |
| 128 | + su2double object, with both values and derivatives, with a float. The |
| 129 | + correct form to use is `CHECK(SU2_TYPE::GetValue(y) == Approx(64.0))` |
| 130 | + (and you can also use `REQUIRE`). |
| 131 | + |
| 132 | +## Real Examples |
| 133 | + |
| 134 | +If you find the above examples a bit simplistic, then you can refer to the |
| 135 | +following files to find simple, real-world unit-tests: |
| 136 | + |
| 137 | +- `UnitTests/SU2_CFD/numerics/CNumerics_tests.cpp` |
| 138 | +- `UnitTests/Common/geometry/primal_grid/CPrimalGrid_tests.cpp` |
| 139 | +- `UnitTests/Common/geometry/dual_grid/CDualGrid_tests.cpp` |
| 140 | +- `UnitTests/Common/simple_ad_test.cpp` |
| 141 | +- `UnitTests/Common/simple_directdiff_test.cpp` |
| 142 | + |
| 143 | +## FAQ |
| 144 | + |
| 145 | +### Where can I learn more about unit tests? |
| 146 | + |
| 147 | +The following two books are great introductions to software testing: |
| 148 | + |
| 149 | ++ "Working Effectively with Legacy Code," by Michael Feathers |
| 150 | ++ "Modern C++ Programming with Test-Driven Development," by Jeff Langr |
0 commit comments