Unit tests

Executing Unit Tests

Unit tests in PARALLAX are managed using pFUnit. To enable unit testing, PARALLAX must be compiled with the -DPARALLAX_ENABLE_UTESTS=ON flag. This will generate executables named PARALLAX-utests-<name>, which can be run individually or collectively using CTest. Since PARALLAX includes some MPI-parallelized features, tests may need to be executed with up to 4 MPI processes.

Writing Unit Tests

Any new feature developed in PARALLAX should be accompanied by unit tests. While PARALLAX may not strictly follow the textbook definition of a unit test, we consider unit tests at the module level, meaning each module and its functionalities should ideally have corresponding unit tests. When designing unit tests, please consider the following guidelines:

  • Meaningful and Simple: Tests should be meaningful and cover important functionality, but they should also be as simple as possible—no need for unnecessary complexity.
  • Clarity: Ensure that tests are written in a way that is easy to understand.
  • Modularise tests: Create distinct tests for different aspects of functionality to ensure clear isolation and easier debugging.
  • Edge Cases: Don't forget to include edge cases in your tests to ensure robustness.
  • Test Accuracy: Be mindful of floating-point precision when comparing values. Use appropriate tolerances or error margins to account for small variations due to floating-point arithmetic.
  • Efficiency: The tests should be computationally lightweight. Use the smallest possible use cases or resolutions, and ensure the test completes within a few seconds—ideally, no more than 1 minute. This ensures the pipeline remains efficient, especially on shared runners.
  • Avoid file I/O: If possible try to avoid reading/writing external files.
  • MPI Limitations: Tests should not use more than 4 MPI processes.
  • Execution Messages: Include a message that indicates when a test has been executed (see template below) to provide feedback during test runs.

Here you may find a template for a unit test file.

module test_foo_m
    !! Contains tests for foo module
    use MPI
    use pfunit
    use screen_io_m, only : get_stdout
    implicit none
contains

    @test(npes = [4]) 
    ! This test uses MPI
    subroutine test_foo(this)
        !! Unit test for foo, testing specific aspect
        use MPI
        use foo_m, only : foo_t
        implicit none
        class (MpiTestMethod), intent(inout) :: this
        integer :: comm_world, rank, nprocs, ierr

        comm_world = this%getMpiCommunicator()
        call MPI_COMM_rank(comm_world, rank, ierr)
        call MPI_COMM_size(comm_world, nprocs, ierr)

        ! Write message to screen, to show that this test was actually run
        if (rank == 0) then
            write(get_stdout(),'(A80)') &
                'test_foo ' //repeat('-',80)
        endif

        ! Perform tests via assert functions
        ...
        @assertEqual(returned_value, expected_value)
        ...

        ! Write message to screen, to show that this test was actually completed
        if (rank == 0) then
            write(get_stdout(),'(A80)') &
            'test_foo '//repeat('-',80)
        endif

    end subroutine

    ...

    ! Maybe add further test, testing further aspects of foo

end module